diff --git a/.gitignore b/.gitignore index eeb9f3ff9..df27f7137 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ -.idea/ -android-nrf-mesh-library/meshprovisioner/.classpath -android-nrf-mesh-library/meshprovisioner/.project -android-nrf-mesh-library/meshprovisioner/.settings/ \ No newline at end of file +*.iml +.idea +.gradle +/local.properties +.DS_Store +build/ +/captures +.externalNativeBuild \ No newline at end of file diff --git a/Example/nrf-mesh/app/.gitignore b/Example/nrf-mesh/app/.gitignore index 7ad0f3591..99962ed78 100644 --- a/Example/nrf-mesh/app/.gitignore +++ b/Example/nrf-mesh/app/.gitignore @@ -1,5 +1,9 @@ -/build -/release *.iml -/.idea -local.properties \ No newline at end of file +.idea +.gradle +/local.properties +.DS_Store +build/ +release/ +/captures +.externalNativeBuild \ No newline at end of file diff --git a/Example/nrf-mesh/app/build.gradle b/Example/nrf-mesh/app/build.gradle index c091f65a1..506e0e9af 100644 --- a/Example/nrf-mesh/app/build.gradle +++ b/Example/nrf-mesh/app/build.gradle @@ -23,16 +23,16 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 - buildToolsVersion '28.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.0' defaultConfig { applicationId "no.nordicsemi.android.nrfmeshprovisioner" minSdkVersion 18 - targetSdkVersion 28 - versionCode 29 - versionName "1.2.1" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + targetSdkVersion 29 + versionCode 48 + versionName "2.0.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true vectorDrawables.useSupportLibrary = true } @@ -62,34 +62,30 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') // Required -- JUnit 4 framework testImplementation 'junit:junit:4.12' + implementation 'androidx.test:runner:1.2.0' - def support_lib_version = "28.0.0" - implementation "com.android.support:appcompat-v7:$support_lib_version" - implementation "com.android.support:design:$support_lib_version" - implementation "com.android.support:recyclerview-v7:$support_lib_version" - implementation "com.android.support:cardview-v7:$support_lib_version" - implementation "com.android.support:support-v4:$support_lib_version" - implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha2' + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'com.google.android.material:material:1.1.0-alpha08' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1' // Butter Knife - implementation 'com.jakewharton:butterknife:8.8.1' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' + implementation 'com.jakewharton:butterknife:10.1.0' + annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0' // Brings the new BluetoothLeScanner API to older platforms - implementation 'no.nordicsemi.android.support.v18:scanner:1.1.0' + implementation 'no.nordicsemi.android.support.v18:scanner:1.4.0' implementation 'no.nordicsemi.android:ble:1.2.0' // Log Bluetooth LE events in nRF Logger - compileOnly 'com.android.support:multidex:1.0.3' + implementation 'androidx.multidex:multidex:2.0.1' // Lifecycle extensions + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha02' - def arch_version = "1.1.1" - implementation "android.arch.lifecycle:extensions:$arch_version" - implementation "android.arch.persistence.room:runtime:$arch_version" - annotationProcessor "android.arch.persistence.room:compiler:$arch_version" - - implementation 'com.google.dagger:dagger:2.17' - implementation "com.google.dagger:dagger-android:2.17" - implementation "com.google.dagger:dagger-android-support:2.17" - annotationProcessor 'com.google.dagger:dagger-compiler:2.17' - annotationProcessor "com.google.dagger:dagger-android-processor:2.17" + implementation 'com.google.dagger:dagger:2.21' + implementation 'com.google.dagger:dagger-android:2.21' + implementation 'com.google.dagger:dagger-android-support:2.21' + annotationProcessor 'com.google.dagger:dagger-compiler:2.21' + annotationProcessor "com.google.dagger:dagger-android-processor:2.21" implementation project(':meshprovisioner') } diff --git a/Example/nrf-mesh/app/src/main/AndroidManifest.xml b/Example/nrf-mesh/app/src/main/AndroidManifest.xml index f9b21e5ab..39c416065 100644 --- a/Example/nrf-mesh/app/src/main/AndroidManifest.xml +++ b/Example/nrf-mesh/app/src/main/AndroidManifest.xml @@ -45,9 +45,9 @@ tools:ignore="GoogleAppIndexingWarning"> + android:theme="@style/AppTheme.SplashScreen"> @@ -57,50 +57,89 @@ android:name=".MainActivity" android:icon="@drawable/ic_feature_mesh" android:label="@string/feature_name" - android:launchMode="singleTop"> + android:launchMode="singleTop" + android:windowSoftInputMode="adjustPan"> + android:configChanges="orientation|screenSize" + android:launchMode="singleTop"/> + + + + + + + + + + + + + - \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BaseModelConfigurationActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BaseModelConfigurationActivity.java deleted file mode 100644 index 148374b91..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BaseModelConfigurationActivity.java +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Copyright (c) 2018, Nordic Semiconductor - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package no.nordicsemi.android.nrfmeshprovisioner; - -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import butterknife.BindView; -import butterknife.ButterKnife; -import dagger.android.DispatchingAndroidInjector; -import no.nordicsemi.android.meshprovisioner.Group; -import no.nordicsemi.android.meshprovisioner.MeshNetwork; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; -import no.nordicsemi.android.meshprovisioner.transport.ConfigModelAppBind; -import no.nordicsemi.android.meshprovisioner.transport.ConfigModelAppStatus; -import no.nordicsemi.android.meshprovisioner.transport.ConfigModelAppUnbind; -import no.nordicsemi.android.meshprovisioner.transport.ConfigModelPublicationSet; -import no.nordicsemi.android.meshprovisioner.transport.ConfigModelPublicationStatus; -import no.nordicsemi.android.meshprovisioner.transport.ConfigModelSubscriptionAdd; -import no.nordicsemi.android.meshprovisioner.transport.ConfigModelSubscriptionDelete; -import no.nordicsemi.android.meshprovisioner.transport.ConfigModelSubscriptionStatus; -import no.nordicsemi.android.meshprovisioner.transport.Element; -import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; -import no.nordicsemi.android.meshprovisioner.transport.MeshModel; -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.meshprovisioner.utils.CompositionDataParser; -import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; -import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.meshprovisioner.utils.PublicationSettings; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.BoundAppKeysAdapter; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.GroupAddressAdapter; -import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentConfigStatus; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentDisconnected; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentGroupSubscription; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentTransactionStatus; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ModelConfigurationViewModel; -import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; -import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; -import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; - -public abstract class BaseModelConfigurationActivity extends AppCompatActivity implements Injectable, - DialogFragmentGroupSubscription.DialogFragmentSubscriptionAddressListener, - BoundAppKeysAdapter.OnItemClickListener, - ItemTouchHelperAdapter, - DialogFragmentDisconnected.DialogFragmentDisconnectedListener { - - private static final String DIALOG_FRAGMENT_CONFIGURATION_STATUS = "DIALOG_FRAGMENT_CONFIGURATION_STATUS"; - private static final String PROGRESS_BAR_STATE = "PROGRESS_BAR_STATE"; - - @Inject - DispatchingAndroidInjector mDispatchingAndroidInjector; - - @Inject - ViewModelProvider.Factory mViewModelFactory; - - @BindView(R.id.action_bind_app_key) - Button mActionBindAppKey; - @BindView(R.id.bound_keys) - TextView mAppKeyView; - @BindView(R.id.unbind_hint) - TextView mUnbindHint; - - @BindView(R.id.action_set_publication) - Button mActionSetPublication; - @BindView(R.id.action_clear_publication_set) - Button mActionClearPublication; - @BindView(R.id.publish_address) - TextView mPublishAddressView; - - @BindView(R.id.subscription_address_card) - View mContainerSubscribe; - @BindView(R.id.action_subscribe_address) - Button mActionSubscribe; - @BindView(R.id.subscribe_addresses) - TextView mSubscribeAddressView; - @BindView(R.id.subscribe_hint) - TextView mSubscribeHint; - - @BindView(R.id.configuration_progress_bar) - ProgressBar mProgressbar; - - protected Handler mHandler; - protected ModelConfigurationViewModel mViewModel; - protected List mGroupAddress = new ArrayList<>(); - protected List mKeyIndexes = new ArrayList<>(); - protected GroupAddressAdapter mSubscriptionAdapter; - protected BoundAppKeysAdapter mBoundAppKeyAdapter; - protected Button mActionRead; - private RecyclerView recyclerViewBoundKeys, recyclerViewAddresses; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_model_configuration); - ButterKnife.bind(this); - mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(ModelConfigurationViewModel.class); - mHandler = new Handler(); - - final MeshModel meshModel = mViewModel.getSelectedModel().getValue(); - //noinspection ConstantConditions - final String modelName = meshModel.getModelName(); - - // Set up views - final Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - //noinspection ConstantConditions - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(modelName); - final int modelId = meshModel.getModelId(); - getSupportActionBar().setSubtitle(getString(R.string.model_id, CompositionDataParser.formatModelIdentifier(modelId, true))); - - recyclerViewAddresses = findViewById(R.id.recycler_view_addresses); - recyclerViewAddresses.setLayoutManager(new LinearLayoutManager(this)); - final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); - final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); - itemTouchHelper.attachToRecyclerView(recyclerViewAddresses); - mSubscriptionAdapter = new GroupAddressAdapter(this, mViewModel.getMeshManagerApi().getMeshNetwork(), mViewModel.getSelectedModel()); - recyclerViewAddresses.setAdapter(mSubscriptionAdapter); - - recyclerViewBoundKeys = findViewById(R.id.recycler_view_bound_keys); - recyclerViewBoundKeys.setLayoutManager(new LinearLayoutManager(this)); - final ItemTouchHelper.Callback itemTouchHelperCallbackKeys = new RemovableItemTouchHelperCallback(this); - final ItemTouchHelper itemTouchHelperKeys = new ItemTouchHelper(itemTouchHelperCallbackKeys); - itemTouchHelperKeys.attachToRecyclerView(recyclerViewBoundKeys); - mBoundAppKeyAdapter = new BoundAppKeysAdapter(this, mViewModel.getSelectedModel()); - recyclerViewBoundKeys.setAdapter(mBoundAppKeyAdapter); - mBoundAppKeyAdapter.setOnItemClickListener(this); - - mActionBindAppKey.setOnClickListener(v -> { - final Intent bindAppKeysIntent = new Intent(BaseModelConfigurationActivity.this, ManageAppKeysActivity.class); - bindAppKeysIntent.putExtra(Utils.EXTRA_DATA, Utils.BIND_APP_KEY); - startActivityForResult(bindAppKeysIntent, ManageAppKeysActivity.SELECT_APP_KEY); - }); - - mPublishAddressView.setText(R.string.none); - mActionSetPublication.setOnClickListener(v -> { - final MeshModel model = mViewModel.getSelectedModel().getValue(); - handleAppKeyBind(model); - }); - - mActionClearPublication.setOnClickListener(v -> clearPublication()); - - mActionSubscribe.setOnClickListener(v -> { - //noinspection ConstantConditions - final ArrayList groups = new ArrayList<>(mViewModel.getGroups().getValue()); - final DialogFragmentGroupSubscription fragmentSubscriptionAddress = DialogFragmentGroupSubscription.newInstance(groups); - fragmentSubscriptionAddress.show(getSupportFragmentManager(), null); - }); - - mViewModel.getSelectedModel().observe(this, model -> { - if (model != null) { - updateAppStatusUi(model); - updatePublicationUi(model); - updateSubscriptionUi(model); - } - }); - - mViewModel.getTransactionStatus().observe(this, transactionFailedLiveData -> { - hideProgressBar(); - final String message = getString(R.string.operation_timed_out); - DialogFragmentTransactionStatus fragmentMessage = DialogFragmentTransactionStatus.newInstance("Transaction Failed", message); - fragmentMessage.show(getSupportFragmentManager(), null); - }); - - mViewModel.isConnectedToProxy().observe(this, aBoolean -> { - if (aBoolean != null && !aBoolean) { - final DialogFragmentDisconnected dialogFragmentDisconnected = DialogFragmentDisconnected.newInstance(getString(R.string.title_disconnected_error), - getString(R.string.disconnected_network_rationale)); - dialogFragmentDisconnected.show(getSupportFragmentManager(), null); - } - }); - - mViewModel.getMeshMessageLiveData().observe(this, this::updateMeshMessage); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; - } - return false; - } - - @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(PROGRESS_BAR_STATE, mProgressbar.getVisibility() == View.VISIBLE); - } - - @Override - protected void onRestoreInstanceState(final Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - if (savedInstanceState.getBoolean(PROGRESS_BAR_STATE)) { - mProgressbar.setVisibility(View.VISIBLE); - disableClickableViews(); - } else { - mProgressbar.setVisibility(View.INVISIBLE); - enableClickableViews(); - } - } - - @Override - protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) { - case ManageAppKeysActivity.SELECT_APP_KEY: - if (resultCode == RESULT_OK) { - final ApplicationKey appKey = data.getParcelableExtra(ManageAppKeysActivity.RESULT_APP_KEY); - if (appKey != null) { - bindAppKey(appKey.getKeyIndex()); - } - } - break; - case PublicationSettingsActivity.SET_PUBLICATION_SETTINGS: - if (resultCode == RESULT_OK) { - setPublication(data); - } - break; - } - } - - @Override - protected void onStop() { - super.onStop(); - if (isFinishing()) { - mHandler.removeCallbacksAndMessages(null); - } - } - - @Override - public void setGroupSubscription(@NonNull final String name, final int address) { - final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); - final Group group = new Group(address, network.getMeshUUID()); - group.setName(name); - network.addGroup(group); - subscribe(address); - } - - @Override - public void setGroupSubscription(@NonNull final Group group) { - subscribe(group.getGroupAddress()); - } - - private void subscribe(final int address) { - final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); - if (meshNode != null) { - final Element element = mViewModel.getSelectedElement().getValue(); - if (element != null) { - final int elementAddress = element.getElementAddress(); - final MeshModel model = mViewModel.getSelectedModel().getValue(); - if (model != null) { - final int modelIdentifier = model.getModelId(); - final ConfigModelSubscriptionAdd configModelSubscriptionAdd = new ConfigModelSubscriptionAdd(elementAddress, address, modelIdentifier); - mViewModel.getMeshManagerApi().sendMeshMessage(meshNode.getUnicastAddress(), configModelSubscriptionAdd); - showProgressbar(); - } - } - } - } - - @Override - public void onItemDismiss(final RemovableViewHolder viewHolder) { - final int position = viewHolder.getAdapterPosition(); - if (viewHolder instanceof GroupAddressAdapter.ViewHolder) { - deleteSubscription(position); - } else if (viewHolder instanceof BoundAppKeysAdapter.ViewHolder) { - unbindAppKey(position); - } - } - - @Override - public void onItemDismissFailed(final RemovableViewHolder viewHolder) { - - } - - @Override - public void onItemClick(final int position, final ApplicationKey appKey) { - - } - - @Override - public void onDisconnected() { - finish(); - } - - protected void handleAppKeyBind(final MeshModel model) { - if (model != null && !model.getBoundAppKeyIndexes().isEmpty()) { - final Intent publicationSettings = new Intent(this, PublicationSettingsActivity.class); - startActivityForResult(publicationSettings, PublicationSettingsActivity.SET_PUBLICATION_SETTINGS); - } else { - Toast.makeText(this, R.string.no_app_keys_bound, Toast.LENGTH_LONG).show(); - } - } - - private void bindAppKey(final int appKeyIndex) { - final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); - if (meshNode != null) { - final Element element = mViewModel.getSelectedElement().getValue(); - if (element != null) { - final MeshModel model = mViewModel.getSelectedModel().getValue(); - if (model != null) { - final ConfigModelAppBind configModelAppUnbind = new ConfigModelAppBind(element.getElementAddress(), model.getModelId(), appKeyIndex); - mViewModel.getMeshManagerApi().sendMeshMessage(meshNode.getUnicastAddress(), configModelAppUnbind); - showProgressbar(); - } - } - } - } - - private void unbindAppKey(final int position) { - if (mBoundAppKeyAdapter.getItemCount() != 0) { - final ApplicationKey appKey = mBoundAppKeyAdapter.getAppKey(position); - final int keyIndex = appKey.getKeyIndex();//getAppKeyIndex(appKey); - final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); - if (meshNode != null) { - final Element element = mViewModel.getSelectedElement().getValue(); - if (element != null) { - final MeshModel model = mViewModel.getSelectedModel().getValue(); - if (model != null) { - final ConfigModelAppUnbind configModelAppUnbind = new ConfigModelAppUnbind(element.getElementAddress(), model.getModelId(), keyIndex); - mViewModel.getMeshManagerApi().sendMeshMessage(meshNode.getUnicastAddress(), configModelAppUnbind); - showProgressbar(); - } - } - } - } - } - - private void setPublication(final Intent data) { - final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); - if (node != null) { - final Element element = mViewModel.getSelectedElement().getValue(); - if (element != null) { - final MeshModel model = mViewModel.getSelectedModel().getValue(); - if (model != null) { - final int publishAddress = data.getIntExtra(PublicationSettingsActivity.RESULT_PUBLISH_ADDRESS, 0); - final int appKeyIndex = data.getIntExtra(PublicationSettingsActivity.RESULT_APP_KEY_INDEX, -1); - final boolean credentialFlag = data.getBooleanExtra(PublicationSettingsActivity.RESULT_CREDENTIAL_FLAG, false); - final int publishTtl = data.getIntExtra(PublicationSettingsActivity.RESULT_PUBLISH_TTL, 0); - final int publicationSteps = data.getIntExtra(PublicationSettingsActivity.RESULT_PUBLICATION_STEPS, 0); - final int resolution = data.getIntExtra(PublicationSettingsActivity.RESULT_PUBLICATION_RESOLUTION, 0); - final int publishRetransmitCount = data.getIntExtra(PublicationSettingsActivity.RESULT_PUBLISH_RETRANSMIT_COUNT, 0); - final int publishRetransmitIntervalSteps = data.getIntExtra(PublicationSettingsActivity.RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS, 0); - if (appKeyIndex > -1) { - try { - final ConfigModelPublicationSet configModelPublicationSet = new ConfigModelPublicationSet(element.getElementAddress(), - publishAddress, appKeyIndex, credentialFlag, publishTtl, - publicationSteps, resolution, publishRetransmitCount, publishRetransmitIntervalSteps, model.getModelId()); - if (!model.getBoundAppKeyIndexes().isEmpty()) { - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), configModelPublicationSet); - showProgressbar(); - } else { - Toast.makeText(this, getString(R.string.error_no_app_keys_bound), Toast.LENGTH_SHORT).show(); - } - } catch (Exception ex) { - ex.printStackTrace(); - } - } - } - } - } - } - - private void clearPublication() { - final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); - if (meshNode != null) { - final Element element = mViewModel.getSelectedElement().getValue(); - if (element != null) { - final MeshModel model = mViewModel.getSelectedModel().getValue(); - if (model != null) { - if (!model.getBoundAppKeyIndexes().isEmpty()) { - final int address = MeshParserUtils.DISABLED_PUBLICATION_ADDRESS; - final int appKeyIndex = model.getPublicationSettings().getAppKeyIndex(); - final boolean credentialFlag = model.getPublicationSettings().getCredentialFlag(); - final ConfigModelPublicationSet configModelPublicationSet = new ConfigModelPublicationSet(element.getElementAddress(), address, appKeyIndex, - credentialFlag, 0, 0, 0, 0, 0, model.getModelId()); - mViewModel.getMeshManagerApi().sendMeshMessage(meshNode.getUnicastAddress(), configModelPublicationSet); - showProgressbar(); - } else { - Toast.makeText(this, R.string.no_app_keys_bound, Toast.LENGTH_LONG).show(); - } - } - } - } - } - - private void deleteSubscription(final int position) { - if (mSubscriptionAdapter.getItemCount() != 0) { - final int address = mGroupAddress.get(position); - final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); - if (meshNode != null) { - final Element element = mViewModel.getSelectedElement().getValue(); - if (element != null) { - final MeshModel model = mViewModel.getSelectedModel().getValue(); - if (model != null) { - final ConfigModelSubscriptionDelete configModelAppUnbind = new ConfigModelSubscriptionDelete(element.getElementAddress(), - address, model.getModelId()); - mViewModel.getMeshManagerApi().sendMeshMessage(meshNode.getUnicastAddress(), configModelAppUnbind); - showProgressbar(); - } - } - } - } - } - - protected final void showProgressbar() { - disableClickableViews(); - mProgressbar.setVisibility(View.VISIBLE); - } - - protected final void hideProgressBar() { - enableClickableViews(); - mProgressbar.setVisibility(View.INVISIBLE); - mHandler.removeCallbacks(mOperationTimeout); - } - - private final Runnable mOperationTimeout = () -> { - hideProgressBar(); - DialogFragmentTransactionStatus fragmentMessage = DialogFragmentTransactionStatus.newInstance(getString(R.string.title_transaction_failed), getString(R.string.operation_timed_out)); - fragmentMessage.show(getSupportFragmentManager(), null); - }; - - protected void enableClickableViews() { - mActionBindAppKey.setEnabled(true); - mActionSetPublication.setEnabled(true); - mActionClearPublication.setEnabled(true); - mActionSubscribe.setEnabled(true); - - if (mActionRead != null && !mActionRead.isEnabled()) - mActionRead.setEnabled(true); - - } - - protected void disableClickableViews() { - mActionBindAppKey.setEnabled(false); - mActionSetPublication.setEnabled(false); - mActionClearPublication.setEnabled(false); - mActionSubscribe.setEnabled(false); - - if (mActionRead != null) - mActionRead.setEnabled(false); - - - } - - /** - * Update the mesh message - * - * @param meshMessage {@link MeshMessage} mesh message status - */ - protected void updateMeshMessage(final MeshMessage meshMessage) { - if (meshMessage instanceof ConfigModelAppStatus) { - final ConfigModelAppStatus status = (ConfigModelAppStatus) meshMessage; - if (!status.isSuccessful()) { - DialogFragmentConfigStatus fragmentAppKeyBindStatus = DialogFragmentConfigStatus. - newInstance(getString(R.string.title_appkey_status), status.getStatusCodeName()); - fragmentAppKeyBindStatus.show(getSupportFragmentManager(), DIALOG_FRAGMENT_CONFIGURATION_STATUS); - } - } else if (meshMessage instanceof ConfigModelPublicationStatus) { - final ConfigModelPublicationStatus status = (ConfigModelPublicationStatus) meshMessage; - if (!status.isSuccessful()) { - DialogFragmentConfigStatus fragmentAppKeyBindStatus = DialogFragmentConfigStatus. - newInstance(getString(R.string.title_publlish_address_status), status.getStatusCodeName()); - fragmentAppKeyBindStatus.show(getSupportFragmentManager(), DIALOG_FRAGMENT_CONFIGURATION_STATUS); - } - } else if (meshMessage instanceof ConfigModelSubscriptionStatus) { - final ConfigModelSubscriptionStatus status = (ConfigModelSubscriptionStatus) meshMessage; - if (!status.isSuccessful()) { - DialogFragmentConfigStatus fragmentAppKeyBindStatus = DialogFragmentConfigStatus. - newInstance(getString(R.string.title_publlish_address_status), status.getStatusCodeName()); - fragmentAppKeyBindStatus.show(getSupportFragmentManager(), DIALOG_FRAGMENT_CONFIGURATION_STATUS); - } - } - hideProgressBar(); - } - - private void updateAppStatusUi(final MeshModel meshModel) { - final List keys = meshModel.getBoundAppKeyIndexes(); - mKeyIndexes.clear(); - mKeyIndexes.addAll(keys); - if (!keys.isEmpty()) { - mUnbindHint.setVisibility(View.VISIBLE); - mAppKeyView.setVisibility(View.GONE); - recyclerViewBoundKeys.setVisibility(View.VISIBLE); - } else { - mUnbindHint.setVisibility(View.GONE); - mAppKeyView.setVisibility(View.VISIBLE); - recyclerViewBoundKeys.setVisibility(View.GONE); - } - } - - private void updatePublicationUi(final MeshModel meshModel) { - final PublicationSettings publicationSettings = meshModel.getPublicationSettings(); - if (publicationSettings != null) { - final int publishAddress = publicationSettings.getPublishAddress(); - if (publishAddress != MeshParserUtils.DISABLED_PUBLICATION_ADDRESS) { - mPublishAddressView.setText(MeshAddress.formatAddress(publishAddress, true)); - mActionClearPublication.setVisibility(View.VISIBLE); - } else { - mPublishAddressView.setText(R.string.none); - mActionClearPublication.setVisibility(View.GONE); - } - } - } - - private void updateSubscriptionUi(final MeshModel meshModel) { - final List subscriptionAddresses = meshModel.getSubscribedAddresses(); - mGroupAddress.clear(); - mGroupAddress.addAll(subscriptionAddresses); - if (!subscriptionAddresses.isEmpty()) { - mSubscribeHint.setVisibility(View.VISIBLE); - mSubscribeAddressView.setVisibility(View.GONE); - recyclerViewAddresses.setVisibility(View.VISIBLE); - } else { - mSubscribeHint.setVisibility(View.GONE); - mSubscribeAddressView.setVisibility(View.VISIBLE); - recyclerViewAddresses.setVisibility(View.GONE); - } - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupCallbacks.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupCallbacks.java new file mode 100644 index 000000000..e2b7228d6 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupCallbacks.java @@ -0,0 +1,85 @@ +package no.nordicsemi.android.nrfmeshprovisioner; + +import java.util.UUID; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.meshprovisioner.Group; + +public interface GroupCallbacks { + + /** + * Creates a group + * + * @return {@link Group} + */ + default Group createGroup() { + //Default implementation + return null; + } + + /** + * Creates a group + * + * @param name Group name + * @return {@link Group} + */ + default Group createGroup(@NonNull final String name) { + //Default implementation + return null; + } + + /** + * Creates a group + * + * @param name Group name + * @return {@link Group} + */ + default Group createGroup(@NonNull final String name, final int address) { + //Default implementation + return null; + } + + /** + * Creates a group + * + * @param uuid virtual label + * @param name group name + * @return {@link Group} + */ + default Group createGroup(@NonNull final UUID uuid, final String name) { + //Default implementation + return null; + } + + /** + * Adds a group + * + * @param name Name + * @param address Address + * @return true if successful or false otherwise + */ + default boolean onGroupAdded(@NonNull final String name, final int address) { + //Default implementation + return false; + } + + /** + * Adds a group + * + * @param group {@link Group} + * @return true if successful or false otherwise + */ + default boolean onGroupAdded(@NonNull final Group group) { + //Default implementation + return false; + } + + /** + * Subscribe to a group + * + * @param group {@link Group} + */ + default void subscribe(final Group group) { + //Default implementation + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupControlsActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupControlsActivity.java index e14c8c6a4..aa9476f16 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupControlsActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupControlsActivity.java @@ -22,16 +22,8 @@ package no.nordicsemi.android.nrfmeshprovisioner; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -42,15 +34,20 @@ import javax.inject.Inject; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; import no.nordicsemi.android.meshprovisioner.Group; import no.nordicsemi.android.meshprovisioner.MeshNetwork; -import no.nordicsemi.android.meshprovisioner.models.ConfigurationServerModel; -import no.nordicsemi.android.meshprovisioner.models.GenericLevelServerModel; -import no.nordicsemi.android.meshprovisioner.models.GenericOnOffServerModel; import no.nordicsemi.android.meshprovisioner.models.SigModelParser; import no.nordicsemi.android.meshprovisioner.models.VendorModel; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; import no.nordicsemi.android.meshprovisioner.transport.Element; import no.nordicsemi.android.meshprovisioner.transport.GenericLevelSetUnacknowledged; import no.nordicsemi.android.meshprovisioner.transport.GenericOnOffSetUnacknowledged; @@ -63,7 +60,13 @@ import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.nrfmeshprovisioner.adapter.SubGroupAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.ble.ScannerActivity; import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentError; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.BottomSheetDetailsDialogFragment; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.BottomSheetLevelDialogFragment; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.BottomSheetOnOffDialogFragment; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.BottomSheetVendorDialogFragment; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.GroupControlsViewModel; @@ -102,7 +105,7 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { final RecyclerView recyclerViewSubGroups = findViewById(R.id.recycler_view_grouped_models); recyclerViewSubGroups.setLayoutManager(new LinearLayoutManager(this)); groupAdapter = new SubGroupAdapter(this, - mViewModel.getMeshManagerApi().getMeshNetwork(), + mViewModel.getNetworkLiveData().getMeshNetwork(), mViewModel.getSelectedGroup(), mViewModel.isConnectedToProxy()); groupAdapter.setOnItemClickListener(this); @@ -111,11 +114,11 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { mViewModel.getSelectedGroup().observe(this, group -> { if (group != null) { getSupportActionBar().setTitle(group.getName()); - getSupportActionBar().setSubtitle(MeshAddress.formatAddress(group.getGroupAddress(), true)); + getSupportActionBar().setSubtitle(MeshAddress.formatAddress(group.getAddress(), true)); } }); - mViewModel.getMeshNetworkLiveData().observe(this, meshNetworkLiveData -> { + mViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { if (groupAdapter.getModelCount() > 0) { noModelsConfigured.setVisibility(View.INVISIBLE); if (groupAdapter.getItemCount() > 0) { @@ -135,13 +138,13 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { final BottomSheetDetailsDialogFragment fragment = (BottomSheetDetailsDialogFragment) getSupportFragmentManager().findFragmentByTag(DETAILS_FRAGMENT); if (fragment != null) { final Group group = mViewModel.getSelectedGroup().getValue(); - final MeshNetwork meshNetwork = mViewModel.getMeshManagerApi().getMeshNetwork(); + final MeshNetwork meshNetwork = mViewModel.getNetworkLiveData().getMeshNetwork(); final ArrayList elements = new ArrayList<>(meshNetwork.getElements(group)); fragment.updateAdapter(group, elements); } }); - mViewModel.getNrfMeshRepository().getMeshMessageLiveData().observe(this, meshMessage -> { + mViewModel.getMeshMessage().observe(this, meshMessage -> { if (meshMessage instanceof VendorModelMessageStatus) { final VendorModelMessageStatus status = (VendorModelMessageStatus) meshMessage; final BottomSheetVendorDialogFragment fragment = (BottomSheetVendorDialogFragment) getSupportFragmentManager().findFragmentByTag(VENDOR_FRAGMENT); @@ -156,7 +159,7 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { @Override public boolean onCreateOptionsMenu(final Menu menu) { - if (mViewModel.getProvisionedNodes().getValue() != null && !mViewModel.getProvisionedNodes().getValue().isEmpty()) { + if (mViewModel.getNodes().getValue() != null && !mViewModel.getNodes().getValue().isEmpty()) { final Boolean isConnectedToNetwork = mViewModel.isConnectedToProxy().getValue(); if (isConnectedToNetwork != null && isConnectedToNetwork) { getMenuInflater().inflate(R.menu.menu_group_controls_disconnect, menu); @@ -219,19 +222,19 @@ public void toggle(final int appKeyIndex, final int modelId, final boolean isChe return; final MeshMessage meshMessage; - final ApplicationKey applicationKey = mViewModel.getMeshManagerApi().getMeshNetwork().getAppKey(appKeyIndex); - final int tid = mViewModel.getMeshManagerApi().getMeshNetwork().getSelectedProvisioner().getSequenceNumber(); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + final ApplicationKey applicationKey = network.getAppKey(appKeyIndex); + final int tid = network.getSelectedProvisioner().getSequenceNumber(); switch (modelId) { case SigModelParser.GENERIC_ON_OFF_SERVER: - meshMessage = new GenericOnOffSetUnacknowledged(applicationKey.getKey(), isChecked, tid); - mViewModel.getMeshManagerApi().sendMeshMessage(group.getGroupAddress(), meshMessage); + meshMessage = new GenericOnOffSetUnacknowledged(applicationKey, isChecked, tid); + sendMessage(group.getAddress(), meshMessage); break; case SigModelParser.GENERIC_LEVEL_SERVER: - meshMessage = new GenericLevelSetUnacknowledged(applicationKey.getKey(), isChecked ? 32767 : -32768, tid); - mViewModel.getMeshManagerApi().sendMeshMessage(group.getGroupAddress(), meshMessage); + meshMessage = new GenericLevelSetUnacknowledged(applicationKey, isChecked ? 32767 : -32768, tid); + sendMessage(group.getAddress(), meshMessage); break; } - } @Override @@ -240,10 +243,12 @@ public void toggle(final int keyIndex, final boolean state, final int transition if (group == null) return; - final ApplicationKey applicationKey = mViewModel.getMeshManagerApi().getMeshNetwork().getAppKey(keyIndex); - final int tid = mViewModel.getMeshManagerApi().getMeshNetwork().getSelectedProvisioner().getSequenceNumber(); - final MeshMessage meshMessage = new GenericOnOffSetUnacknowledged(applicationKey.getKey(), state, tid, transitionSteps, transitionStepResolution, delay); - mViewModel.getMeshManagerApi().sendMeshMessage(group.getGroupAddress(), meshMessage); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + final ApplicationKey applicationKey = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(keyIndex); + final int tid = network.getSelectedProvisioner().getSequenceNumber(); + final MeshMessage meshMessage = new GenericOnOffSetUnacknowledged(applicationKey, + state, tid, transitionSteps, transitionStepResolution, delay); + sendMessage(group.getAddress(), meshMessage); } @Override @@ -252,30 +257,37 @@ public void toggleLevel(final int keyIndex, final int level, final int transitio if (group == null) return; - final ApplicationKey applicationKey = mViewModel.getMeshManagerApi().getMeshNetwork().getAppKey(keyIndex); - final int tid = mViewModel.getMeshManagerApi().getMeshNetwork().getSelectedProvisioner().getSequenceNumber(); - final MeshMessage meshMessage = new GenericLevelSetUnacknowledged(applicationKey.getKey(), transitionSteps, transitionStepResolution, delay, level, tid); - mViewModel.getMeshManagerApi().sendMeshMessage(group.getGroupAddress(), meshMessage); + + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + final ApplicationKey applicationKey = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(keyIndex); + final int tid = mViewModel.getNetworkLiveData().getMeshNetwork().getSelectedProvisioner().getSequenceNumber(); + final MeshMessage meshMessage = new GenericLevelSetUnacknowledged(applicationKey, transitionSteps, transitionStepResolution, delay, level, tid); + sendMessage(group.getAddress(), meshMessage); + } } private void editGroup() { final Group group = mViewModel.getSelectedGroup().getValue(); - final MeshNetwork meshNetwork = mViewModel.getMeshManagerApi().getMeshNetwork(); - final ArrayList elements = new ArrayList<>(meshNetwork.getElements(group)); - final BottomSheetDetailsDialogFragment onOffFragment = BottomSheetDetailsDialogFragment.getInstance(group, elements); - onOffFragment.show(getSupportFragmentManager(), DETAILS_FRAGMENT); + final MeshNetwork meshNetwork = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (meshNetwork != null) { + final ArrayList elements = new ArrayList<>(meshNetwork.getElements(group)); + final BottomSheetDetailsDialogFragment onOffFragment = BottomSheetDetailsDialogFragment.getInstance(group, elements); + onOffFragment.show(getSupportFragmentManager(), DETAILS_FRAGMENT); + } } @Override public void editModelItem(@NonNull final Element element, @NonNull final MeshModel model) { final Boolean isConnectedToNetwork = mViewModel.isConnectedToProxy().getValue(); if (isConnectedToNetwork != null && isConnectedToNetwork) { - final ProvisionedMeshNode node = mViewModel.getMeshManagerApi().getMeshNetwork().getProvisionedNode(element.getElementAddress()); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + final ProvisionedMeshNode node = network.getNode(element.getElementAddress()); if (node != null) { mViewModel.setSelectedMeshNode(node); mViewModel.setSelectedElement(element); mViewModel.setSelectedModel(model); - startActivity(model); + mViewModel.navigateToModelActivity(this, model); } } else { Toast.makeText(this, R.string.disconnected_network_rationale, Toast.LENGTH_SHORT).show(); @@ -284,30 +296,8 @@ public void editModelItem(@NonNull final Element element, @NonNull final MeshMod @Override public void onGroupNameChanged(@NonNull final Group group) { - mViewModel.getMeshManagerApi().getMeshNetwork().updateGroup(group); - } - - /** - * Start activity based on the type of the model - * - *

This way we can seperate the ui logic for different activities

- * - * @param model model - */ - private void startActivity(final MeshModel model) { - final Intent intent; - if (model instanceof ConfigurationServerModel) { - intent = new Intent(this, ConfigurationServerActivity.class); - } else if (model instanceof GenericOnOffServerModel) { - intent = new Intent(this, GenericOnOffServerActivity.class); - } else if (model instanceof GenericLevelServerModel) { - intent = new Intent(this, GenericLevelServerActivity.class); - } else if (model instanceof VendorModel) { - intent = new Intent(this, VendorModelActivity.class); - } else { - intent = new Intent(this, ModelConfigurationActivity.class); - } - startActivity(intent); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + network.updateGroup(group); } @Override @@ -320,14 +310,17 @@ public void sendVendorModelMessage(final int modelId, final int keyIndex, final if (model == null) return; - final ApplicationKey appKey = mViewModel.getMeshManagerApi().getMeshNetwork().getAppKey(keyIndex); - final MeshMessage message; - if (acknowledged) { - message = new VendorModelMessageAcked(appKey.getKey(), modelId, model.getCompanyIdentifier(), opCode, parameters); - } else { - message = new VendorModelMessageUnacked(appKey.getKey(), modelId, model.getCompanyIdentifier(), opCode, parameters); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + final ApplicationKey appKey = network.getAppKey(keyIndex); + final MeshMessage message; + if (acknowledged) { + message = new VendorModelMessageAcked(appKey, modelId, model.getCompanyIdentifier(), opCode, parameters); + } else { + message = new VendorModelMessageUnacked(appKey, modelId, model.getCompanyIdentifier(), opCode, parameters); + } + sendMessage(group.getAddress(), message); } - mViewModel.getMeshManagerApi().sendMeshMessage(group.getGroupAddress(), message); } private VendorModel getModel(final int modelId, final int appKeyIndex) { @@ -342,4 +335,14 @@ private VendorModel getModel(final int modelId, final int appKeyIndex) { return null; } + + private void sendMessage(final int address, final MeshMessage meshMessage) { + try { + mViewModel.getMeshManagerApi().createMeshPdu(address, meshMessage); + } catch (IllegalArgumentException ex) { + final DialogFragmentError message = DialogFragmentError. + newInstance(getString(R.string.title_error), ex.getMessage()); + message.show(getSupportFragmentManager(), null); + } + } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupsFragment.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupsFragment.java index 5c8ad57d1..5ab8292a4 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupsFragment.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GroupsFragment.java @@ -22,29 +22,30 @@ package no.nordicsemi.android.nrfmeshprovisioner; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; -import android.content.Context; +import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import java.util.UUID; + import javax.inject.Inject; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.Group; @@ -52,7 +53,6 @@ import no.nordicsemi.android.nrfmeshprovisioner.adapter.GroupAdapter; import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentCreateGroup; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.SharedViewModel; import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; @@ -61,9 +61,7 @@ public class GroupsFragment extends Fragment implements Injectable, ItemTouchHelperAdapter, GroupAdapter.OnItemClickListener, - DialogFragmentCreateGroup.DialogFragmentCreateGroupListener { - - private static final String TAG = GroupsFragment.class.getSimpleName(); + GroupCallbacks { private SharedViewModel mViewModel; @@ -71,25 +69,23 @@ public class GroupsFragment extends Fragment implements Injectable, ViewModelProvider.Factory mViewModelFactory; @BindView(R.id.container) - View container; - @BindView(R.id.fab_add_group) - FloatingActionButton fab; + CoordinatorLayout container; + @BindView(android.R.id.empty) + View mEmptyView; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setHasOptionsMenu(true); } @Nullable @Override public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { - final View rootView = inflater.inflate(R.layout.fragment_groups, null); - ButterKnife.bind(this, rootView); - + @SuppressLint("InflateParams") final View rootView = inflater.inflate(R.layout.fragment_groups, null); mViewModel = ViewModelProviders.of(requireActivity(), mViewModelFactory).get(SharedViewModel.class); + ButterKnife.bind(this, rootView); - final View noGroupsConfiguredView = rootView.findViewById(R.id.no_groups_configured); + final ExtendedFloatingActionButton fab = rootView.findViewById(R.id.fab_add_group); // Configure the recycler view final RecyclerView recyclerViewGroups = rootView.findViewById(R.id.recycler_view_groups); @@ -103,18 +99,18 @@ public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final adapter.setOnItemClickListener(this); recyclerViewGroups.setAdapter(adapter); - mViewModel.getMeshNetworkLiveData().observe(this, meshNetworkLiveData -> { + mViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { if (meshNetworkLiveData != null) { - if(meshNetworkLiveData.getMeshNetwork().getGroups().isEmpty()){ - noGroupsConfiguredView.setVisibility(View.VISIBLE); + if (meshNetworkLiveData.getMeshNetwork().getGroups().isEmpty()) { + mEmptyView.setVisibility(View.VISIBLE); } else { - noGroupsConfiguredView.setVisibility(View.INVISIBLE); + mEmptyView.setVisibility(View.INVISIBLE); } } }); mViewModel.getGroups().observe(this, groups -> { - final MeshNetwork network = mViewModel.getMeshNetworkLiveData().getMeshNetwork(); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); adapter.updateAdapter(network, groups); }); @@ -123,41 +119,23 @@ public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final fragmentCreateGroup.show(getChildFragmentManager(), null); }); - return rootView; - - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - if (mViewModel.getProvisionedNodes().getValue() != null && !mViewModel.getProvisionedNodes().getValue().isEmpty()) { - final Boolean isConnectedToNetwork = mViewModel.isConnectedToProxy().getValue(); - if (isConnectedToNetwork != null && isConnectedToNetwork) { - inflater.inflate(R.menu.disconnect, menu); - } else { - inflater.inflate(R.menu.connect, menu); + recyclerViewGroups.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { + super.onScrolled(recyclerView, dx, dy); + final LinearLayoutManager m = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (m != null) { + if (m.findFirstCompletelyVisibleItemPosition() == 0) { + fab.extend(true); + } else { + fab.shrink(true); + } + } } - } - } + }); - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - final int id = item.getItemId(); - switch (id) { - case R.id.action_connect: - final Intent intent = new Intent(requireActivity(), ScannerActivity.class); - intent.putExtra(Utils.EXTRA_DATA_PROVISIONING_SERVICE, false); - startActivity(intent); - return true; - case R.id.action_disconnect: - mViewModel.disconnect(); - return true; - } - return false; - } + return rootView; - @Override - public void onAttach(final Context context) { - super.onAttach(context); } @Override @@ -169,31 +147,66 @@ public void onItemClick(final int address) { @Override public void onItemDismiss(final RemovableViewHolder viewHolder) { final int position = viewHolder.getAdapterPosition(); - final MeshNetwork network = mViewModel.getMeshNetworkLiveData().getMeshNetwork(); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); final Group group = network.getGroups().get(position); - if(network.getModels(group).size() == 0) { + if (network.getModels(group).size() == 0) { network.removeGroup(group); - final String message = getString(R.string.group_deleted, group.getName()); - displaySnackBar(message); + displaySnackBar(group); } } @Override public void onItemDismissFailed(final RemovableViewHolder viewHolder) { final String message = getString(R.string.error_group_unsubscribe_to_delete); - displaySnackBar(message); + mViewModel.displaySnackBar(requireActivity(), container, message, Snackbar.LENGTH_LONG); } + @Override + public Group createGroup() { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + return network.createGroup(network.getSelectedProvisioner(), "Mesh Group"); + } - private void displaySnackBar(final String message){ - Snackbar.make(container, message, Snackbar.LENGTH_LONG) - .setActionTextColor(getResources().getColor(R.color.colorPrimaryDark )) - .show(); + @Override + public Group createGroup(@NonNull final String name) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + return network.createGroup(network.getSelectedProvisioner(), name); + } + + @Override + public Group createGroup(@NonNull final UUID uuid, final String name) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + return network.createGroup(uuid, null, name); } @Override - public boolean createGroup(@NonNull final String name, final int address) { - final MeshNetwork network = mViewModel.getMeshNetworkLiveData().getMeshNetwork(); - return network.addGroup(address, name); + public boolean onGroupAdded(@NonNull final String name, final int address) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + final Group group = network.createGroup(network.getSelectedProvisioner(), address, name); + if (group != null) { + return network.addGroup(group); + } + return false; + } + + @Override + public boolean onGroupAdded(@NonNull final Group group) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + return network.addGroup(group); + } + + private void displaySnackBar(final Group group) { + final String message = getString(R.string.group_deleted, group.getName()); + Snackbar.make(container, message, Snackbar.LENGTH_LONG) + .setActionTextColor(getResources().getColor(R.color.colorPrimaryDark)) + .setAction(R.string.undo, v -> { + mEmptyView.setVisibility(View.INVISIBLE); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + network.addGroup(group); + } + + }) + .show(); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/MainActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/MainActivity.java index b695088a6..5a5335549 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/MainActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/MainActivity.java @@ -22,34 +22,36 @@ package no.nordicsemi.android.nrfmeshprovisioner; -import android.arch.lifecycle.ViewModelProvider; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.BottomNavigationView; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import android.view.Menu; import android.view.MenuItem; -import android.view.View; + +import com.google.android.material.bottomnavigation.BottomNavigationView; import javax.inject.Inject; -import butterknife.BindView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; import butterknife.ButterKnife; import dagger.android.AndroidInjector; import dagger.android.DispatchingAndroidInjector; import dagger.android.support.HasSupportFragmentInjector; import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.SharedViewModel; public class MainActivity extends AppCompatActivity implements Injectable, HasSupportFragmentInjector, BottomNavigationView.OnNavigationItemSelectedListener, BottomNavigationView.OnNavigationItemReselectedListener { - private static final int TAB_COUNT = 3; private static final String CURRENT_FRAGMENT = "CURRENT_FRAGMENT"; @Inject @@ -58,49 +60,68 @@ public class MainActivity extends AppCompatActivity implements Injectable, @Inject ViewModelProvider.Factory mViewModelFactory; - private BottomNavigationView mBottomNavigationView; - private NetworkFragment mNetworkFragment; private GroupsFragment mGroupsFragment; + private ProxyFilterFragment mProxyFilterFragment; private Fragment mSettingsFragment; + private SharedViewModel mViewModel; @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + mViewModel = ViewModelProviders.of(this).get(SharedViewModel.class); ButterKnife.bind(this); final Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); + //noinspection ConstantConditions getSupportActionBar().setTitle(R.string.app_name); mNetworkFragment = (NetworkFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_network); mGroupsFragment = (GroupsFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_groups); + mProxyFilterFragment = (ProxyFilterFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_proxy); mSettingsFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_settings); - mBottomNavigationView = findViewById(R.id.bottom_navigation_view); + final BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation_view); - mBottomNavigationView.setOnNavigationItemSelectedListener(this); - mBottomNavigationView.setOnNavigationItemReselectedListener(this); + bottomNavigationView.setOnNavigationItemSelectedListener(this); + bottomNavigationView.setOnNavigationItemReselectedListener(this); if (savedInstanceState == null) { - onNavigationItemSelected(mBottomNavigationView.getMenu().findItem(R.id.action_network)); + onNavigationItemSelected(bottomNavigationView.getMenu().findItem(R.id.action_network)); } else { - mBottomNavigationView.setSelectedItemId(savedInstanceState.getInt(CURRENT_FRAGMENT)); + bottomNavigationView.setSelectedItemId(savedInstanceState.getInt(CURRENT_FRAGMENT)); } } @Override - public void onBackPressed() { - if(getSupportFragmentManager().getFragments().size() > TAB_COUNT) { - getSupportFragmentManager().popBackStack(); + public boolean onCreateOptionsMenu(final Menu menu) { + final Boolean isConnectedToNetwork = mViewModel.isConnectedToProxy().getValue(); + if (isConnectedToNetwork != null && isConnectedToNetwork) { + getMenuInflater().inflate(R.menu.disconnect, menu); } else { - super.onBackPressed(); + getMenuInflater().inflate(R.menu.connect, menu); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + final int id = item.getItemId(); + switch (id) { + case R.id.action_connect: + mViewModel.navigateToScannerActivity(this, false, Utils.CONNECT_TO_NETWORK, false); + return true; + case R.id.action_disconnect: + mViewModel.disconnect(); + return true; } + return false; } @Override - protected void onDestroy() { - super.onDestroy(); + public void onBackPressed() { + super.onBackPressed(); } @Override @@ -114,13 +135,16 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { final FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); switch (id) { case R.id.action_network: - ft.show(mNetworkFragment).hide(mGroupsFragment).hide(mSettingsFragment); + ft.show(mNetworkFragment).hide(mGroupsFragment).hide(mProxyFilterFragment).hide(mSettingsFragment); + break; + case R.id.action_groups: + ft.hide(mNetworkFragment).show(mGroupsFragment).hide(mProxyFilterFragment).hide(mSettingsFragment); break; - case R.id.action_scanner: - ft.hide(mNetworkFragment).show(mGroupsFragment).hide(mSettingsFragment); + case R.id.action_proxy: + ft.hide(mNetworkFragment).hide(mGroupsFragment).show(mProxyFilterFragment).hide(mSettingsFragment); break; case R.id.action_settings: - ft.hide(mNetworkFragment).hide(mGroupsFragment).show(mSettingsFragment); + ft.hide(mNetworkFragment).hide(mGroupsFragment).hide(mProxyFilterFragment).show(mSettingsFragment); break; } ft.commit(); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ManageAppKeysActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ManageAppKeysActivity.java deleted file mode 100644 index 6831a85f4..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ManageAppKeysActivity.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (c) 2018, Nordic Semiconductor - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package no.nordicsemi.android.nrfmeshprovisioner; - -import android.app.Activity; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import butterknife.BindView; -import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.ManageAppKeyAdapter; -import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentAddAppKey; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentEditAppKey; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ManageAppKeysViewModel; -import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; -import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; -import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; - -public class ManageAppKeysActivity extends AppCompatActivity implements Injectable, ManageAppKeyAdapter.OnItemClickListener, - DialogFragmentAddAppKey.DialogFragmentAddAppKeysListener, - DialogFragmentEditAppKey.DialogFragmentEditAppKeysListener, - ItemTouchHelperAdapter { - - public static final String RESULT_APP_KEY = "RESULT_APP_KEY"; - public static final String RESULT_APP_KEY_INDEX = "RESULT_APP_KEY_INDEX"; - public static final String RESULT_APP_KEY_LIST_SIZE = "RESULT_APP_KEY_LIST_SIZE"; - public static final String APP_KEYS = "APP_KEYS"; - public static final int SELECT_APP_KEY = 2011; //Random number - public static final int MANAGE_APP_KEYS = 2012; //Random number - private static final String MAIN_ACTIVITY = ".MainActivity"; - - @Inject - ViewModelProvider.Factory mViewModelFactory; - - //UI Bindings - @BindView(android.R.id.empty) - View mEmptyView; - @BindView(R.id.container) - View container; - - private ManageAppKeysViewModel mViewModel; - private ManageAppKeyAdapter mAdapter; - - @Override - protected void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_manage_app_keys); - mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(ManageAppKeysViewModel.class); - - //Bind ui - ButterKnife.bind(this); - - final Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - final FloatingActionButton fab = findViewById(R.id.fab); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - final RecyclerView appKeysRecyclerView = findViewById(R.id.recycler_view_app_keys); - appKeysRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - final DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(appKeysRecyclerView.getContext(), DividerItemDecoration.VERTICAL); - appKeysRecyclerView.addItemDecoration(dividerItemDecoration); - appKeysRecyclerView.setItemAnimator(new DefaultItemAnimator()); - - //noinspection ConstantConditions - switch (getIntent().getExtras().getInt(Utils.EXTRA_DATA)) { - case Utils.MANAGE_APP_KEY: - getSupportActionBar().setTitle(R.string.title_manage_app_keys); - final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); - final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); - itemTouchHelper.attachToRecyclerView(appKeysRecyclerView); - mAdapter = new ManageAppKeyAdapter(this, mViewModel.getMeshNetworkLiveData()); - mAdapter.setOnItemClickListener(this); - appKeysRecyclerView.setAdapter(mAdapter); - setUpObserver(); - break; - case Utils.ADD_APP_KEY: - getSupportActionBar().setTitle(R.string.title_select_app_key); - fab.hide(); - mAdapter = new ManageAppKeyAdapter(this, mViewModel.getMeshNetworkLiveData()); - mAdapter.setOnItemClickListener(this); - appKeysRecyclerView.setAdapter(mAdapter); - setUpObserver(); - break; - case Utils.BIND_APP_KEY: - case Utils.PUBLICATION_APP_KEY: - getSupportActionBar().setTitle(R.string.title_select_app_key); - fab.hide(); - //Get selected mesh node - final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); - if (node != null) { - final List applicationKeys = new ArrayList<>(node.getAddedApplicationKeys().values()); - if (!applicationKeys.isEmpty()) { - mAdapter = new ManageAppKeyAdapter(this, applicationKeys); - mAdapter.setOnItemClickListener(this); - appKeysRecyclerView.setAdapter(mAdapter); - } else { - final TextView textView = mEmptyView.findViewById(R.id.rationale); - textView.setText(R.string.no_added_app_keys_rationale); - mEmptyView.setVisibility(View.VISIBLE); - } - } - break; - } - - fab.setOnClickListener(v -> { - final DialogFragmentAddAppKey dialogFragmentAddAppKey = DialogFragmentAddAppKey.newInstance(null); - dialogFragmentAddAppKey.show(getSupportFragmentManager(), null); - }); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; - } - return false; - } - - @Override - public void onBackPressed() { - final int extras = getIntent().getExtras().getInt(Utils.EXTRA_DATA); - switch (extras) { - case Utils.MANAGE_APP_KEY: - Intent returnIntent = new Intent(); - returnIntent.putExtra(RESULT_APP_KEY_LIST_SIZE, mAdapter.getItemCount()); - setResult(Activity.RESULT_OK, returnIntent); - finish(); - break; - default: - super.onBackPressed(); - break; - } - } - - @Override - public void onItemClick(final int position, final ApplicationKey appKey) { - final int extras = getIntent().getExtras().getInt(Utils.EXTRA_DATA); - switch (extras) { - case Utils.MANAGE_APP_KEY: - final DialogFragmentEditAppKey dialogFragmentEditAppKey = DialogFragmentEditAppKey.newInstance(position, appKey); - dialogFragmentEditAppKey.show(getSupportFragmentManager(), null); - break; - default: - Intent returnIntent = new Intent(); - returnIntent.putExtra(RESULT_APP_KEY_INDEX, position); - returnIntent.putExtra(RESULT_APP_KEY, appKey); - setResult(Activity.RESULT_OK, returnIntent); - finish(); - break; - } - } - - @Override - public void onAppKeysUpdated(final int position, final String appKey) { - mViewModel.getMeshNetworkLiveData().updateAppKey(position, appKey); - } - - @Override - public void onAppKeyAdded(final String appKey) { - mViewModel.getMeshNetworkLiveData().addAppKey(appKey); - } - - @Override - public void onItemDismiss(final RemovableViewHolder viewHolder) { - final ApplicationKey key = (ApplicationKey) viewHolder.getSwipeableView().getTag(); - mViewModel.getMeshNetworkLiveData().removeAppKey(key); - displaySnackBar(viewHolder.getAdapterPosition(), key); - // Show the empty view - final boolean empty = mAdapter.getItemCount() == 0; - if (empty) { - mEmptyView.setVisibility(View.VISIBLE); - } - } - - @Override - public void onItemDismissFailed(final RemovableViewHolder viewHolder) { - - } - - private void setUpObserver() { - mViewModel.getMeshNetworkLiveData().observe(this, networkLiveData -> { - if (networkLiveData != null) { - final List keys = networkLiveData.getAppKeys(); - if (keys != null) { - mEmptyView.setVisibility(keys.isEmpty() ? View.VISIBLE : View.GONE); - } - } - }); - } - - private void displaySnackBar(final int key, final ApplicationKey appKey) { - - Snackbar.make(container, getString(R.string.app_key_deleted), Snackbar.LENGTH_LONG) - .setAction(getString(R.string.undo), view -> { - mEmptyView.setVisibility(View.INVISIBLE); - mViewModel.getMeshNetworkLiveData().addAppKey(appKey); - }) - .setActionTextColor(getResources().getColor(R.color.colorPrimaryDark)) - .show(); - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NetworkFragment.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NetworkFragment.java index aa22cf56a..842e7e430 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NetworkFragment.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NetworkFragment.java @@ -22,158 +22,200 @@ package no.nordicsemi.android.nrfmeshprovisioner; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; +import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + import javax.inject.Inject; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.NodeAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.ble.ScannerActivity; import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentConfigError; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentError; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentDeleteNode; +import no.nordicsemi.android.nrfmeshprovisioner.node.NodeConfigurationActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.adapter.NodeAdapter; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.SharedViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; import static android.app.Activity.RESULT_OK; public class NetworkFragment extends Fragment implements Injectable, - NodeAdapter.OnItemClickListener { + NodeAdapter.OnItemClickListener, + ItemTouchHelperAdapter, + DialogFragmentDeleteNode.DialogFragmentDeleteNodeListener { - private static final String TAG_SCANNER_FRAGMENT = "SCANNER_FRAGMENT"; private SharedViewModel mViewModel; @Inject ViewModelProvider.Factory mViewModelFactory; - @BindView(R.id.main_content) - View container; + @BindView(R.id.container) + CoordinatorLayout container; @BindView(R.id.recycler_view_provisioned_nodes) RecyclerView mRecyclerViewNodes; - - private NodeAdapter mAdapter; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } + private NodeAdapter mNodeAdapter; @Nullable @Override public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { - final View rootView = inflater.inflate(R.layout.fragment_network, null); + @SuppressLint("InflateParams") final View rootView = inflater.inflate(R.layout.fragment_network, null); + mViewModel = ViewModelProviders.of(requireActivity(), mViewModelFactory).get(SharedViewModel.class); ButterKnife.bind(this, rootView); - final FloatingActionButton fab = rootView.findViewById(R.id.fab_add_node); + final ExtendedFloatingActionButton fab = rootView.findViewById(R.id.fab_add_node); final View noNetworksConfiguredView = rootView.findViewById(R.id.no_networks_configured); - mViewModel = ViewModelProviders.of(requireActivity(), mViewModelFactory).get(SharedViewModel.class); - // Configure the recycler view - mAdapter = new NodeAdapter(getActivity(), mViewModel.getProvisionedNodes()); - mAdapter.setOnItemClickListener(this); + mNodeAdapter = new NodeAdapter(requireContext(), mViewModel.getNodes()); + mNodeAdapter.setOnItemClickListener(this); mRecyclerViewNodes.setLayoutManager(new LinearLayoutManager(getContext())); final DividerItemDecoration decoration = new DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL); mRecyclerViewNodes.addItemDecoration(decoration); - mRecyclerViewNodes.setAdapter(mAdapter); + final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); + final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); + itemTouchHelper.attachToRecyclerView(mRecyclerViewNodes); + mRecyclerViewNodes.setAdapter(mNodeAdapter); // Create view model containing utility methods for scanning - mViewModel.getProvisionedNodes().observe(this, nodes -> { - if (!nodes.isEmpty()) { + mViewModel.getNodes().observe(this, nodes -> { + if (nodes != null && !nodes.isEmpty()) { noNetworksConfiguredView.setVisibility(View.GONE); } else { noNetworksConfiguredView.setVisibility(View.VISIBLE); } - mAdapter.notifyDataSetChanged(); + requireActivity().invalidateOptionsMenu(); }); - mViewModel.getProvisionedNodes().observe(this, provisionedNodes -> requireActivity().invalidateOptionsMenu()); - mViewModel.isConnectedToProxy().observe(this, isConnected -> { if (isConnected != null) { requireActivity().invalidateOptionsMenu(); } }); - mViewModel.getConnectedMeshNodeAddress().observe(this, unicastAddress -> mAdapter.selectConnectedMeshNode(unicastAddress)); + mRecyclerViewNodes.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { + super.onScrolled(recyclerView, dx, dy); + final LinearLayoutManager m = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (m != null) { + if (m.findFirstCompletelyVisibleItemPosition() == 0) { + fab.extend(true); + } else { + fab.shrink(true); + } + } + } + }); fab.setOnClickListener(v -> { - final Intent intent = new Intent(requireActivity(), ScannerActivity.class); + final Intent intent = new Intent(requireContext(), ScannerActivity.class); intent.putExtra(Utils.EXTRA_DATA_PROVISIONING_SERVICE, true); startActivityForResult(intent, Utils.PROVISIONING_SUCCESS); }); return rootView; + } + @Override + public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + handleActivityResult(requestCode, resultCode, data); } @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - if (mViewModel.getProvisionedNodes().getValue() != null && !mViewModel.getProvisionedNodes().getValue().isEmpty()) { - final Boolean isConnectedToNetwork = mViewModel.isConnectedToProxy().getValue(); - if (isConnectedToNetwork != null && isConnectedToNetwork) { - inflater.inflate(R.menu.disconnect, menu); - } else { - inflater.inflate(R.menu.connect, menu); - } + public void onConfigureClicked(final ProvisionedMeshNode node) { + mViewModel.setSelectedMeshNode(node); + final Intent meshConfigurationIntent = new Intent(getActivity(), NodeConfigurationActivity.class); + requireActivity().startActivity(meshConfigurationIntent); + } + + @Override + public void onItemDismiss(final RemovableViewHolder viewHolder) { + final int position = viewHolder.getAdapterPosition(); + if (!mNodeAdapter.isEmpty()) { + final DialogFragmentDeleteNode fragmentDeleteNode = DialogFragmentDeleteNode.newInstance(position); + fragmentDeleteNode.show(getChildFragmentManager(), null); } } @Override - public boolean onOptionsItemSelected(final MenuItem item) { - final int id = item.getItemId(); - switch (id) { - case R.id.action_connect: - final Intent intent = new Intent(requireActivity(), ScannerActivity.class); - intent.putExtra(Utils.EXTRA_DATA_PROVISIONING_SERVICE, false); - startActivityForResult(intent, Utils.CONNECT_TO_NETWORK); - return true; - case R.id.action_disconnect: - mViewModel.disconnect(); - return true; + public void onItemDismissFailed(final RemovableViewHolder viewHolder) { + //Do nothing + } + + @Override + public void onNodeDeleteConfirmed(final int position) { + final ProvisionedMeshNode node = mNodeAdapter.getItem(position); + if (mViewModel.getNetworkLiveData().getMeshNetwork().deleteNode(node)) { + mViewModel.displaySnackBar(requireActivity(), container, getString(R.string.node_deleted), Snackbar.LENGTH_LONG); } - return false; } @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { + public void onNodeDeleteCancelled(final int position) { + mNodeAdapter.notifyItemChanged(position); + } + + private void handleActivityResult(final int requestCode, final int resultCode, @NonNull final Intent data) { if (requestCode == Utils.PROVISIONING_SUCCESS) { if (resultCode == RESULT_OK) { final boolean provisioningSuccess = data.getBooleanExtra(Utils.PROVISIONING_COMPLETED, false); + final DialogFragmentError fragmentConfigError; if (provisioningSuccess) { - final boolean compositionDataReceived = data.getBooleanExtra(Utils.COMPOSITION_DATA_COMPLETED, false); - final boolean appKeyAddCompleted = data.getBooleanExtra(Utils.APP_KEY_ADD_COMPLETED, false); - final DialogFragmentConfigError fragmentConfigError; - if(compositionDataReceived){ - if(!appKeyAddCompleted){ - fragmentConfigError = - DialogFragmentConfigError.newInstance(getString(R.string.title_init_config_error) - , getString(R.string.init_config_error_app_key_msg)); - fragmentConfigError.show(getChildFragmentManager(), null); - } - } else { + final boolean provisionerUnassigned = data.getBooleanExtra(Utils.PROVISIONER_UNASSIGNED, false); + if (provisionerUnassigned) { fragmentConfigError = - DialogFragmentConfigError.newInstance(getString(R.string.title_init_config_error) - , getString(R.string.init_config_error_all)); + DialogFragmentError.newInstance(getString(R.string.title_init_config_error) + , getString(R.string.provisioner_unassigned_msg)); fragmentConfigError.show(getChildFragmentManager(), null); + } else { + final boolean compositionDataReceived = data.getBooleanExtra(Utils.COMPOSITION_DATA_COMPLETED, false); + final boolean defaultTtlGetCompleted = data.getBooleanExtra(Utils.DEFAULT_GET_COMPLETED, false); + final boolean appKeyAddCompleted = data.getBooleanExtra(Utils.APP_KEY_ADD_COMPLETED, false); + final boolean networkRetransmitSetCompleted = data.getBooleanExtra(Utils.NETWORK_TRANSMIT_SET_COMPLETED, false); + final String title = getString(R.string.title_init_config_error); + final String message; + if (compositionDataReceived) { + if (defaultTtlGetCompleted) { + if (appKeyAddCompleted) { + if (!networkRetransmitSetCompleted) { + message = getString(R.string.init_config_error_app_key_msg); + showErrorDialog(title, message); + } + } else { + message = getString(R.string.init_config_error_app_key_msg); + showErrorDialog(title, message); + } + } else { + message = getString(R.string.init_config_error_default_ttl_get_msg); + showErrorDialog(title, message); + } + } else { + message = getString(R.string.init_config_error_all); + showErrorDialog(title, message); + } } } requireActivity().invalidateOptionsMenu(); @@ -181,29 +223,8 @@ public void onActivityResult(final int requestCode, final int resultCode, final } } - @Override - public void onConfigureClicked(final ProvisionedMeshNode node) { - final Boolean isConnectedToProxy = mViewModel.isConnectedToProxy().getValue(); - if (isConnectedToProxy != null && isConnectedToProxy) { - mViewModel.setSelectedMeshNode(node); - final Intent meshConfigurationIntent = new Intent(getActivity(), NodeConfigurationActivity.class); - requireActivity().startActivity(meshConfigurationIntent); - } else { - displaySnackBar(getString(R.string.disconnected_network_rationale)); - } - } - - @Override - public void onDetailsClicked(final ProvisionedMeshNode node) { - final Intent meshConfigurationIntent = new Intent(getActivity(), NodeDetailsActivity.class); - meshConfigurationIntent.putExtra(Utils.EXTRA_DEVICE, node); - requireActivity().startActivity(meshConfigurationIntent); - } - - - private void displaySnackBar(final String message){ - Snackbar.make(container, message, Snackbar.LENGTH_LONG) - .setActionTextColor(getResources().getColor(R.color.colorPrimaryDark )) - .show(); + private void showErrorDialog(@NonNull final String title, @NonNull final String message) { + final DialogFragmentError dialogFragmentError = DialogFragmentError.newInstance(title, message); + dialogFragmentError.show(getChildFragmentManager(), null); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NodeConfigurationActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NodeConfigurationActivity.java deleted file mode 100644 index 07711cb38..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NodeConfigurationActivity.java +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright (c) 2018, Nordic Semiconductor - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package no.nordicsemi.android.nrfmeshprovisioner; - -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.widget.NestedScrollView; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.CardView; -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.ProgressBar; -import android.widget.Switch; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import butterknife.BindView; -import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.models.ConfigurationServerModel; -import no.nordicsemi.android.meshprovisioner.models.GenericLevelServerModel; -import no.nordicsemi.android.meshprovisioner.models.GenericOnOffServerModel; -import no.nordicsemi.android.meshprovisioner.models.VendorModel; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; -import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyAdd; -import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyStatus; -import no.nordicsemi.android.meshprovisioner.transport.ConfigCompositionDataGet; -import no.nordicsemi.android.meshprovisioner.transport.ConfigCompositionDataStatus; -import no.nordicsemi.android.meshprovisioner.transport.ConfigNodeReset; -import no.nordicsemi.android.meshprovisioner.transport.ConfigNodeResetStatus; -import no.nordicsemi.android.meshprovisioner.transport.ConfigProxyGet; -import no.nordicsemi.android.meshprovisioner.transport.ConfigProxySet; -import no.nordicsemi.android.meshprovisioner.transport.ConfigProxyStatus; -import no.nordicsemi.android.meshprovisioner.transport.Element; -import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; -import no.nordicsemi.android.meshprovisioner.transport.MeshModel; -import no.nordicsemi.android.meshprovisioner.transport.NetworkKey; -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigAddAddressToFilter; -import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigFilterStatus; -import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigRemoveAddressFromFilter; -import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigSetFilterType; -import no.nordicsemi.android.meshprovisioner.utils.AddressArray; -import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; -import no.nordicsemi.android.meshprovisioner.utils.ProxyFilter; -import no.nordicsemi.android.meshprovisioner.utils.ProxyFilterType; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.AddedAppKeyAdapter; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.ElementAdapter; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.FilterAddressAdapter; -import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentAppKeyAddStatus; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentFilterAddAddress; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentProxySet; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentResetNode; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentTransactionStatus; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NodeConfigurationViewModel; -import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; -import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; -import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; - -public class NodeConfigurationActivity extends AppCompatActivity implements Injectable, - ElementAdapter.OnItemClickListener, - DialogFragmentAppKeyAddStatus.DialogFragmentAppKeyAddStatusListener, - DialogFragmentProxySet.DialogFragmentProxySetListener, - DialogFragmentFilterAddAddress.DialogFragmentFilterAddressListener, - DialogFragmentResetNode.DialogFragmentNodeResetListener, - AddedAppKeyAdapter.OnItemClickListener, - ItemTouchHelperAdapter { - - private final static String TAG = NodeConfigurationActivity.class.getSimpleName(); - private static final String PROGRESS_BAR_STATE = "PROGRESS_BAR_STATE"; - private static final String PROXY_STATE = "PROXY_STATE"; - private static final String REQUESTED_PROXY_STATE = "REQUESTED_PROXY_STATE"; - private static final String DIALOG_FRAGMENT_APP_KEY_STATUS = "DIALOG_FRAGMENT_APP_KEY_STATUS"; - - @Inject - ViewModelProvider.Factory mViewModelFactory; - - @BindView(R.id.main_container) - NestedScrollView mContainer; - @BindView(R.id.action_get_compostion_data) - Button actionGetCompositionData; - @BindView(R.id.action_add_app_keys) - Button actionAddAppkey; - @BindView(R.id.node_proxy_state_card) - View mProxyStateCard; - @BindView(R.id.proxy_state_summary) - TextView mProxyStateRationaleSummary; - @BindView(R.id.action_get_proxy_state) - Button actionGetProxyState; - @BindView(R.id.action_set_proxy_state) - Button actionSetProxyState; - @BindView(R.id.filter_switch) - Switch actionSwitchFilter; - @BindView(R.id.action_add_address) - Button actionAddFilterAddress; - @BindView(R.id.action_clear_addresses) - Button actionClearFilterAddress; - @BindView(R.id.action_reset_node) - Button actionResetNode; - @BindView(R.id.recycler_view_elements) - RecyclerView mRecyclerViewElements; - @BindView(R.id.composition_data_card) - CardView mCompositionDataCard; - @BindView(R.id.proxy_filter_card) - CardView mProxyFilterCard; - @BindView(R.id.configuration_progress_bar) - ProgressBar mProgressbar; - - private NodeConfigurationViewModel mViewModel; - private Handler mHandler; - private boolean mProxyState; - private boolean mRequestedState = true; - - - private final Runnable mOperationTimeout = this::hideProgressBar; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_mesh_node_configuration); - ButterKnife.bind(this); - mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(NodeConfigurationViewModel.class); - - if (savedInstanceState != null) { - if (savedInstanceState.getBoolean(PROGRESS_BAR_STATE)) { - mProgressbar.setVisibility(View.VISIBLE); - disableClickableViews(); - } else { - mProgressbar.setVisibility(View.INVISIBLE); - enableClickableViews(); - } - mRequestedState = savedInstanceState.getBoolean(PROXY_STATE, true); - mProxyState = savedInstanceState.getBoolean(PROXY_STATE, true); - } - - mHandler = new Handler(); - // Set up views - final Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.title_node_configuration); - getSupportActionBar().setSubtitle(mViewModel.getSelectedMeshNode().getValue().getNodeName()); - - final TextView noElementsFound = findViewById(R.id.no_elements); - final TextView noAppKeysFound = findViewById(R.id.no_app_keys); - final View compositionActionContainer = findViewById(R.id.composition_action_container); - mRecyclerViewElements.setLayoutManager(new LinearLayoutManager(this)); - final ElementAdapter adapter = new ElementAdapter(this, mViewModel.getSelectedMeshNode()); - adapter.setHasStableIds(true); - adapter.setOnItemClickListener(this); - mRecyclerViewElements.setAdapter(adapter); - - final RecyclerView recyclerViewAppKeys = findViewById(R.id.recycler_view_app_keys); - recyclerViewAppKeys.setLayoutManager(new LinearLayoutManager(this)); - recyclerViewAppKeys.setItemAnimator(new DefaultItemAnimator()); - final AddedAppKeyAdapter appKeyAdapter = new AddedAppKeyAdapter(this, mViewModel.getSelectedMeshNode()); - recyclerViewAppKeys.setAdapter(appKeyAdapter); - - final TextView noAddressesAdded = findViewById(R.id.no_addresses); - final RecyclerView recyclerViewAddresses = findViewById(R.id.recycler_view_addresses); - - final Integer unicast = mViewModel.getConnectedMeshNodeAddress().getValue(); - if (unicast != null && unicast == mViewModel.getSelectedMeshNode().getValue().getUnicastAddress()) { - mProxyFilterCard.setVisibility(View.VISIBLE); - recyclerViewAddresses.setLayoutManager(new LinearLayoutManager(this)); - recyclerViewAddresses.setItemAnimator(new DefaultItemAnimator()); - final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); - final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); - itemTouchHelper.attachToRecyclerView(recyclerViewAddresses); - final FilterAddressAdapter addressAdapter = new FilterAddressAdapter(this, mViewModel.getSelectedMeshNode()); - recyclerViewAddresses.setAdapter(addressAdapter); - } - - mViewModel.getSelectedMeshNode().observe(this, meshNode -> { - if (meshNode == null) { - finish(); - return; - } - - if (!meshNode.getElements().isEmpty()) { - compositionActionContainer.setVisibility(View.GONE); - noElementsFound.setVisibility(View.INVISIBLE); - mRecyclerViewElements.setVisibility(View.VISIBLE); - } else { - noElementsFound.setVisibility(View.VISIBLE); - compositionActionContainer.setVisibility(View.VISIBLE); - mRecyclerViewElements.setVisibility(View.INVISIBLE); - } - - if (!meshNode.getAddedApplicationKeys().isEmpty()) { - noAppKeysFound.setVisibility(View.GONE); - recyclerViewAppKeys.setVisibility(View.VISIBLE); - } else { - noAppKeysFound.setVisibility(View.VISIBLE); - recyclerViewAppKeys.setVisibility(View.GONE); - } - final ProxyFilter filter = meshNode.getProxyFilter(); - if (filter != null) { - actionSwitchFilter.setChecked(filter.getFilterType().getType() == ProxyFilterType.WHITE_LIST_FILTER); - if (!filter.getAddresses().isEmpty()) { - noAddressesAdded.setVisibility(View.GONE); - recyclerViewAddresses.setVisibility(View.VISIBLE); - actionClearFilterAddress.setVisibility(View.VISIBLE); - } else { - noAddressesAdded.setVisibility(View.VISIBLE); - recyclerViewAddresses.setVisibility(View.GONE); - actionClearFilterAddress.setVisibility(View.GONE); - } - } - }); - - actionGetCompositionData.setOnClickListener(v -> { - final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); - final ConfigCompositionDataGet configCompositionDataGet = new ConfigCompositionDataGet(); - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), configCompositionDataGet); - showProgressbar(); - }); - - actionAddAppkey.setOnClickListener(v -> { - final Intent addAppKeys = new Intent(NodeConfigurationActivity.this, ManageAppKeysActivity.class); - addAppKeys.putExtra(Utils.EXTRA_DATA, Utils.ADD_APP_KEY); - startActivityForResult(addAppKeys, ManageAppKeysActivity.SELECT_APP_KEY); - }); - - actionGetProxyState.setOnClickListener(v -> { - final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); - final ConfigProxyGet configProxyGet = new ConfigProxyGet(); - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), configProxyGet); - }); - - actionSetProxyState.setOnClickListener(v -> { - final String message; - if (mProxyState) { - message = getString(R.string.proxy_set_off_rationale_summary); - } else { - message = getString(R.string.proxy_set_on_rationale_summary); - } - final DialogFragmentProxySet resetNodeFragment = DialogFragmentProxySet. - newInstance(getString(R.string.title_proxy_state_settings), message, !mProxyState); - resetNodeFragment.show(getSupportFragmentManager(), null); - }); - - actionSwitchFilter.setOnClickListener(v -> { - if (((Switch)v).isChecked()) { - setFilter(new ProxyFilterType(ProxyFilterType.WHITE_LIST_FILTER)); - actionSwitchFilter.setText(R.string.white_list_filter); - } else { - setFilter(new ProxyFilterType(ProxyFilterType.BLACK_LIST_FILTER)); - actionSwitchFilter.setText(R.string.black_list_filter); - } - }); - - actionAddFilterAddress.setOnClickListener(v -> { - final ProxyFilter filter = mViewModel.getSelectedMeshNode().getValue().getProxyFilter(); - final ProxyFilterType filterType; - if (filter == null) { - filterType = new ProxyFilterType(ProxyFilterType.WHITE_LIST_FILTER); - } else { - filterType = filter.getFilterType(); - } - final DialogFragmentFilterAddAddress filterAddAddress = DialogFragmentFilterAddAddress.newInstance(filterType); - filterAddAddress.show(getSupportFragmentManager(), null); - }); - - actionClearFilterAddress.setOnClickListener(v -> removeAddresses()); - - actionResetNode.setOnClickListener(v -> { - final DialogFragmentResetNode resetNodeFragment = DialogFragmentResetNode. - newInstance(getString(R.string.title_reset_node), getString(R.string.reset_node_rationale_summary)); - resetNodeFragment.show(getSupportFragmentManager(), null); - }); - - mViewModel.getTransactionStatus().observe(this, transactionStatus -> { - hideProgressBar(); - final String message; - if (transactionStatus.isIncompleteTimerExpired()) { - message = getString(R.string.segments_not_received_timed_out); - } else { - message = getString(R.string.operation_timed_out); - } - DialogFragmentTransactionStatus fragmentMessage = DialogFragmentTransactionStatus.newInstance(getString(R.string.title_transaction_failed), message); - fragmentMessage.show(getSupportFragmentManager(), null); - }); - - mViewModel.isConnectedToProxy().observe(this, isConnected -> { - if (isConnected != null && !isConnected) - finish(); - }); - - mViewModel.getMeshMessageLiveData().observe(this, this::updateMeshMessage); - - updateProxySettingsCardUi(); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; - } - return false; - } - - @Override - protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == ManageAppKeysActivity.SELECT_APP_KEY) { - if (resultCode == RESULT_OK) { - final ApplicationKey appKey = data.getParcelableExtra(Utils.RESULT_APP_KEY); - if (appKey != null) { - showProgressbar(); - final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); - final NetworkKey networkKey = mViewModel.getMeshManagerApi().getMeshNetwork().getPrimaryNetworkKey(); - final ConfigAppKeyAdd configAppKeyAdd = new ConfigAppKeyAdd(networkKey, appKey); - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), configAppKeyAdd); - } - } - } - } - - @Override - protected void onStop() { - super.onStop(); - if (isFinishing()) { - mHandler.removeCallbacksAndMessages(null); - } - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - } - - @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(PROGRESS_BAR_STATE, mProgressbar.getVisibility() == View.VISIBLE); - outState.putBoolean(PROXY_STATE, mProxyState); - outState.putBoolean(REQUESTED_PROXY_STATE, mRequestedState); - } - - @Override - public void onElementItemClick(final ProvisionedMeshNode meshNode, final Element element, final MeshModel model) { - mViewModel.setSelectedElement(element); - mViewModel.setSelectedModel(model); - startActivity(model); - } - - @Override - public void onAppKeyAddStatusReceived() { - - } - - @Override - public void onItemDismiss(final RemovableViewHolder viewHolder) { - final int position = viewHolder.getAdapterPosition(); - if (viewHolder instanceof FilterAddressAdapter.ViewHolder) { - removeAddress(position); - } - } - - @Override - public void onItemDismissFailed(final RemovableViewHolder viewHolder) { - - } - - @Override - public void onItemClick(final ApplicationKey appKey) { - - } - - @Override - public void onNodeReset() { - try { - final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); - final ConfigNodeReset configNodeReset = new ConfigNodeReset(); - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), configNodeReset); - } catch (Exception ex) { - Log.e(TAG, ex.getMessage()); - } - } - - @Override - public void onProxySet(@ConfigProxySet.ProxyState final int state) { - try { - final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); - final ConfigProxySet configProxySet = new ConfigProxySet(state); - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), configProxySet); - mRequestedState = state == 1; - } catch (Exception ex) { - Log.e(TAG, ex.getMessage()); - } - } - - private void updateProxySettingsCardUi() { - final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); - if (meshNode.getNodeFeatures() != null && meshNode.getNodeFeatures().isProxyFeatureSupported()) { - mProxyStateCard.setVisibility(View.VISIBLE); - updateProxySettingsButtonUi(); - } - } - - private void updateProxySettingsButtonUi() { - if (mProxyState) { - mProxyStateRationaleSummary.setText(R.string.proxy_set_off_rationale); - actionSetProxyState.setText(R.string.action_proxy_state_set_off); - } else { - mProxyStateRationaleSummary.setText(R.string.proxy_set_on_rationale); - actionSetProxyState.setText(R.string.action_proxy_state_set_on); - } - } - - private void showProgressbar() { - disableClickableViews(); - mProgressbar.setVisibility(View.VISIBLE); - } - - private void hideProgressBar() { - mHandler.removeCallbacks(mOperationTimeout); - enableClickableViews(); - mProgressbar.setVisibility(View.INVISIBLE); - } - - private void enableClickableViews() { - actionGetCompositionData.setEnabled(true); - actionAddAppkey.setEnabled(true); - actionGetProxyState.setEnabled(true); - actionSetProxyState.setEnabled(true); - actionSwitchFilter.setEnabled(true); - actionAddFilterAddress.setEnabled(true); - actionClearFilterAddress.setEnabled(true); - actionResetNode.setEnabled(true); - } - - private void disableClickableViews() { - actionGetCompositionData.setEnabled(false); - actionAddAppkey.setEnabled(false); - actionGetProxyState.setEnabled(false); - actionSetProxyState.setEnabled(false); - actionSwitchFilter.setEnabled(false); - actionAddFilterAddress.setEnabled(false); - actionClearFilterAddress.setEnabled(false); - actionResetNode.setEnabled(false); - } - - /** - * Start activity based on the type of the model - * - *

This way we can seperate the ui logic for different activities

- * - * @param model model - */ - private void startActivity(final MeshModel model) { - final Intent intent; - if (model instanceof ConfigurationServerModel) { - intent = new Intent(this, ConfigurationServerActivity.class); - } else if (model instanceof GenericOnOffServerModel) { - intent = new Intent(this, GenericOnOffServerActivity.class); - } else if (model instanceof GenericLevelServerModel) { - intent = new Intent(this, GenericLevelServerActivity.class); - } else if (model instanceof VendorModel) { - intent = new Intent(this, VendorModelActivity.class); - } else { - intent = new Intent(this, ModelConfigurationActivity.class); - } - startActivity(intent); - } - - private void updateMeshMessage(final MeshMessage meshMessage) { - if (meshMessage instanceof ProxyConfigFilterStatus) { - hideProgressBar(); - } - if (meshMessage instanceof ConfigCompositionDataStatus) { - hideProgressBar(); - } else if (meshMessage instanceof ConfigAppKeyStatus) { - if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_APP_KEY_STATUS) == null) { - if (!((ConfigAppKeyStatus) meshMessage).isSuccessful()) { - final DialogFragmentAppKeyAddStatus fragmentAppKeyAddStatus = DialogFragmentAppKeyAddStatus. - newInstance(getString(R.string.title_appkey_status), ((ConfigAppKeyStatus) meshMessage).getStatusCodeName()); - fragmentAppKeyAddStatus.show(getSupportFragmentManager(), DIALOG_FRAGMENT_APP_KEY_STATUS); - } - } - hideProgressBar(); - } else if (meshMessage instanceof ConfigNodeResetStatus) { - hideProgressBar(); - finish(); - } else if (meshMessage instanceof ConfigProxyStatus) { - final ConfigProxyStatus status = (ConfigProxyStatus) meshMessage; - mProxyState = status.isProxyFeatureEnabled(); - updateProxySettingsCardUi(); - hideProgressBar(); - } - } - - @Override - public void addAddresses(final List addresses) { - final ProxyConfigAddAddressToFilter addAddressToFilter = new ProxyConfigAddAddressToFilter(addresses); - mViewModel.getMeshManagerApi().sendMeshMessage(MeshAddress.UNASSIGNED_ADDRESS, addAddressToFilter); - } - - private void removeAddress(final int position) { - final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); - if(meshNode != null) { - final ProxyFilter proxyFilter = meshNode.getProxyFilter(); - if(proxyFilter != null) { - final AddressArray addressArr = proxyFilter.getAddresses().get(position); - final List addresses = new ArrayList<>(); - addresses.add(addressArr); - final ProxyConfigRemoveAddressFromFilter removeAddressFromFilter = new ProxyConfigRemoveAddressFromFilter(addresses); - mViewModel.getMeshManagerApi().sendMeshMessage(MeshAddress.UNASSIGNED_ADDRESS, removeAddressFromFilter); - showProgressbar(); - } - } - } - - private void removeAddresses() { - final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); - if (meshNode != null) { - final ProxyFilter proxyFilter = meshNode.getProxyFilter(); - if (proxyFilter != null) { - if (!proxyFilter.getAddresses().isEmpty()) { - final ProxyConfigRemoveAddressFromFilter removeAddressFromFilter = new ProxyConfigRemoveAddressFromFilter(proxyFilter.getAddresses()); - mViewModel.getMeshManagerApi().sendMeshMessage(MeshAddress.UNASSIGNED_ADDRESS, removeAddressFromFilter); - } - } - } - } - - private void setFilter(final ProxyFilterType filterType) { - showProgressbar(); - final ProxyConfigSetFilterType setFilterType = new ProxyConfigSetFilterType(filterType); - mViewModel.getMeshManagerApi().sendMeshMessage(MeshAddress.UNASSIGNED_ADDRESS, setFilterType); - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/MeshProvisionerActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ProvisioningActivity.java similarity index 67% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/MeshProvisionerActivity.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ProvisioningActivity.java index 60352226b..ec1ef4064 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/MeshProvisionerActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ProvisioningActivity.java @@ -23,17 +23,8 @@ package no.nordicsemi.android.nrfmeshprovisioner; import android.app.Activity; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.Snackbar; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; import android.widget.Button; @@ -42,18 +33,29 @@ import android.widget.ScrollView; import android.widget.TextView; -import java.util.ArrayList; -import java.util.List; +import com.google.android.material.snackbar.Snackbar; + import java.util.Locale; import javax.inject.Inject; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.Provisioner; import no.nordicsemi.android.meshprovisioner.provisionerstates.ProvisioningCapabilities; import no.nordicsemi.android.meshprovisioner.provisionerstates.ProvisioningFailedState; import no.nordicsemi.android.meshprovisioner.provisionerstates.UnprovisionedMeshNode; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; import no.nordicsemi.android.meshprovisioner.utils.AlgorithmType; import no.nordicsemi.android.meshprovisioner.utils.AuthenticationOOBMethods; import no.nordicsemi.android.meshprovisioner.utils.InputOOBAction; @@ -63,37 +65,29 @@ import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; import no.nordicsemi.android.nrfmeshprovisioner.adapter.ProvisioningProgressAdapter; import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentAppKeyAddStatus; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentAuthenticationInput; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentFlags; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentIvIndex; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentKeyIndex; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentNetworkKey; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentNodeName; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentConfigurationComplete; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentProvisioningFailedError; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentSelectOOBType; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentUnicastAddress; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.DialogFragmentNodeName; +import no.nordicsemi.android.nrfmeshprovisioner.utils.ProvisionerStates; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.MeshNetworkLiveData; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.MeshProvisionerViewModel; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ProvisionerProgress; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ProvisioningStatusLiveData; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ProvisioningViewModel; -public class MeshProvisionerActivity extends AppCompatActivity implements Injectable, +public class ProvisioningActivity extends AppCompatActivity implements Injectable, DialogFragmentSelectOOBType.DialogFragmentSelectOOBTypeListener, DialogFragmentAuthenticationInput.ProvisionerInputFragmentListener, DialogFragmentNodeName.DialogFragmentNodeNameListener, - DialogFragmentNetworkKey.DialogFragmentNetworkKeyListener, - DialogFragmentKeyIndex.DialogFragmentKeyIndexListener, - DialogFragmentFlags.DialogFragmentFlagsListener, - DialogFragmentIvIndex.DialogFragmentIvIndexListener, DialogFragmentUnicastAddress.DialogFragmentUnicastAddressListener, DialogFragmentProvisioningFailedError.DialogFragmentProvisioningFailedErrorListener, - DialogFragmentAppKeyAddStatus.DialogFragmentAppKeyAddStatusListener { + DialogFragmentConfigurationComplete.ConfigurationCompleteListener { private static final String DIALOG_FRAGMENT_PROVISIONING_FAILED = "DIALOG_FRAGMENT_PROVISIONING_FAILED"; private static final String DIALOG_FRAGMENT_AUTH_INPUT_TAG = "DIALOG_FRAGMENT_AUTH_INPUT_TAG"; - private static final String DIALOG_FRAGMENT_APP_KEY_STATUS = "DIALOG_FRAGMENT_APP_KEY_STATUS"; + private static final String DIALOG_FRAGMENT_CONFIGURATION_STATUS = "DIALOG_FRAGMENT_CONFIGURATION_STATUS"; @BindView(R.id.container) CoordinatorLayout mCoordinatorLayout; @@ -109,7 +103,7 @@ public class MeshProvisionerActivity extends AppCompatActivity implements Inject @Inject ViewModelProvider.Factory mViewModelFactory; - private MeshProvisionerViewModel mViewModel; + private ProvisioningViewModel mViewModel; @Override protected void onCreate(final Bundle savedInstanceState) { @@ -129,45 +123,56 @@ protected void onCreate(final Bundle savedInstanceState) { getSupportActionBar().setSubtitle(deviceAddress); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(MeshProvisionerViewModel.class); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(ProvisioningViewModel.class); if (savedInstanceState == null) mViewModel.connect(this, device, false); // Set up views final LinearLayout connectivityProgressContainer = findViewById(R.id.connectivity_progress_container); final TextView connectionState = findViewById(R.id.connection_state); - final Button provisioner = findViewById(R.id.action_provision_device); + final Button action_provision = findViewById(R.id.action_provision_device); - final View containerName = findViewById(R.id.container_element_count); - containerName.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(this, R.drawable.ic_vpn_key_black_alpha_24dp)); + final View containerName = findViewById(R.id.container_name); + containerName.findViewById(R.id.image) + .setBackground(ContextCompat.getDrawable(this, R.drawable.ic_label_outline_black_alpha_24dp)); final TextView nameTitle = containerName.findViewById(R.id.title); nameTitle.setText(R.string.summary_name); final TextView nameView = containerName.findViewById(R.id.text); + nameView.setVisibility(View.VISIBLE); containerName.setOnClickListener(v -> { final DialogFragmentNodeName dialogFragmentNodeName = DialogFragmentNodeName.newInstance(deviceName); dialogFragmentNodeName.show(getSupportFragmentManager(), null); }); - final View containerUnicastAddress = findViewById(R.id.container_supported_algorithm); - containerUnicastAddress.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(this, R.drawable.ic_lan_black_alpha_24dp)); + final View containerUnicastAddress = findViewById(R.id.container_unicast); + containerUnicastAddress.findViewById(R.id.image) + .setBackground(ContextCompat.getDrawable(this, R.drawable.ic_lan_black_alpha_24dp)); final TextView unicastAddressTitle = containerUnicastAddress.findViewById(R.id.title); - unicastAddressTitle.setText(R.string.summary_unicast_address); + unicastAddressTitle.setText(R.string.title_unicast_address); final TextView unicastAddressView = containerUnicastAddress.findViewById(R.id.text); + unicastAddressView.setVisibility(View.VISIBLE); containerUnicastAddress.setOnClickListener(v -> { - final int unicastAddress = mViewModel.getMeshNetworkLiveData().getUnicastAddress(); - final DialogFragmentUnicastAddress dialogFragmentFlags = DialogFragmentUnicastAddress.newInstance(unicastAddress); - dialogFragmentFlags.show(getSupportFragmentManager(), null); + + final UnprovisionedMeshNode node = mViewModel.getUnprovisionedMeshNode().getValue(); + if (node != null && node.getProvisioningCapabilities() != null) { + final int elementCount = node.getProvisioningCapabilities().getNumberOfElements(); + final DialogFragmentUnicastAddress dialogFragmentFlags = DialogFragmentUnicastAddress. + newInstance(mViewModel.getNetworkLiveData().getMeshNetwork().getUnicastAddress(), elementCount); + dialogFragmentFlags.show(getSupportFragmentManager(), null); + } }); - final View containerAppKey = findViewById(R.id.container_public_key_type); - containerAppKey.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(this, R.drawable.ic_vpn_key_black_alpha_24dp)); + final View containerAppKey = findViewById(R.id.container_app_keys); + containerAppKey.findViewById(R.id.image) + .setBackground(ContextCompat.getDrawable(this, R.drawable.ic_vpn_key_black_alpha_24dp)); final TextView appKeyTitle = containerAppKey.findViewById(R.id.title); - appKeyTitle.setText(R.string.summary_app_keys); + appKeyTitle.setText(R.string.title_app_keys); final TextView appKeyView = containerAppKey.findViewById(R.id.text); + appKeyView.setVisibility(View.VISIBLE); containerAppKey.setOnClickListener(v -> { - final Intent manageAppKeys = new Intent(MeshProvisionerActivity.this, ManageAppKeysActivity.class); + final Intent manageAppKeys = new Intent(ProvisioningActivity.this, AppKeysActivity.class); manageAppKeys.putExtra(Utils.EXTRA_DATA, Utils.ADD_APP_KEY); - startActivityForResult(manageAppKeys, ManageAppKeysActivity.SELECT_APP_KEY); + startActivityForResult(manageAppKeys, Utils.SELECT_KEY); }); mViewModel.getConnectionState().observe(this, connectionState::setText); @@ -198,7 +203,7 @@ protected void onCreate(final Bundle savedInstanceState) { mViewModel.isReconnecting().observe(this, isReconnecting -> { if (isReconnecting != null && isReconnecting) { - mViewModel.getUnProvisionedMeshNode().removeObservers(this); + mViewModel.getUnprovisionedMeshNode().removeObservers(this); provisioningStatusContainer.setVisibility(View.GONE); container.setVisibility(View.GONE); mProvisioningProgressBar.setVisibility(View.GONE); @@ -208,27 +213,42 @@ protected void onCreate(final Bundle savedInstanceState) { } }); - mViewModel.getMeshNetworkLiveData().observe(this, meshNetworkLiveData -> { + mViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { nameView.setText(meshNetworkLiveData.getNodeName()); - unicastAddressView.setText(getString(R.string.hex_format, String.format(Locale.US, "%04X", meshNetworkLiveData.getUnicastAddress()))); final ApplicationKey applicationKey = meshNetworkLiveData.getSelectedAppKey(); appKeyView.setText(MeshParserUtils.bytesToHex(applicationKey.getKey(), false)); + unicastAddressView.setText(getString(R.string.hex_format, + String.format(Locale.US, "%04X", meshNetworkLiveData.getMeshNetwork().getUnicastAddress()))); }); - mViewModel.getUnProvisionedMeshNode().observe(this, meshNode -> { + mViewModel.getUnprovisionedMeshNode().observe(this, meshNode -> { if (meshNode != null) { - if (meshNode.getProvisioningCapabilities() != null) { + final ProvisioningCapabilities capabilities = meshNode.getProvisioningCapabilities(); + if (capabilities != null) { mProvisioningProgressBar.setVisibility(View.INVISIBLE); - provisioner.setText(R.string.provision_action); - updateCapabilitiesUi(meshNode.getProvisioningCapabilities()); + action_provision.setText(R.string.provision_action); + containerUnicastAddress.setVisibility(View.VISIBLE); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + try { + final int elementCount = capabilities.getNumberOfElements(); + final Provisioner provisioner = network.getSelectedProvisioner(); + final int unicast = network.nextAvailableUnicastAddress(elementCount, provisioner); + network.assignUnicastAddress(unicast); + updateCapabilitiesUi(capabilities); + } catch (IllegalArgumentException ex) { + action_provision.setEnabled(false); + mViewModel.displaySnackBar(this, mCoordinatorLayout, ex.getMessage(), Snackbar.LENGTH_LONG); + } + } } } }); - provisioner.setOnClickListener(v -> { - final UnprovisionedMeshNode node = mViewModel.getUnProvisionedMeshNode().getValue(); + action_provision.setOnClickListener(v -> { + final UnprovisionedMeshNode node = mViewModel.getUnprovisionedMeshNode().getValue(); if (node == null) { - device.setName(mViewModel.getMeshNetworkLiveData().getNodeName()); + device.setName(mViewModel.getNetworkLiveData().getNodeName()); mViewModel.getNrfMeshRepository().identifyNode(device); return; } @@ -244,16 +264,15 @@ protected void onCreate(final Bundle savedInstanceState) { } }); - if(savedInstanceState == null) - mViewModel.getMeshNetworkLiveData().resetSelectedAppKey(); + if (savedInstanceState == null) + mViewModel.getNetworkLiveData().resetSelectedAppKey(); } @Override public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; } return false; } @@ -273,11 +292,11 @@ protected void onDestroy() { @Override protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == ManageAppKeysActivity.SELECT_APP_KEY) { + if (requestCode == Utils.SELECT_KEY) { if (resultCode == RESULT_OK) { - final ApplicationKey appKey = data.getParcelableExtra(ManageAppKeysActivity.RESULT_APP_KEY); + final ApplicationKey appKey = data.getParcelableExtra(AppKeysActivity.RESULT_APP_KEY); if (appKey != null) { - mViewModel.getMeshNetworkLiveData().setSelectedAppKey(appKey); + mViewModel.getNetworkLiveData().setSelectedAppKey(appKey); } } } @@ -285,7 +304,7 @@ protected void onActivityResult(final int requestCode, final int resultCode, fin @Override public void onPinInputComplete(final String pin) { - mViewModel.getMeshManagerApi().setProvisioningConfirmation(pin); + mViewModel.getMeshManagerApi().setProvisioningAuthentication(pin); } @Override @@ -297,33 +316,24 @@ public void onPinInputCanceled() { } @Override - public void onNodeNameUpdated(final String nodeName) { - mViewModel.getMeshNetworkLiveData().setNodeName(nodeName); + public boolean onNodeNameUpdated(@NonNull final String nodeName) { + mViewModel.getNetworkLiveData().setNodeName(nodeName); + return true; } @Override - public void onNetworkKeyGenerated(final String networkKey) { - mViewModel.getMeshNetworkLiveData().setPrimaryNetworkKey(networkKey); - } - - @Override - public void onKeyIndexGenerated(final int keyIndex) { - mViewModel.getMeshNetworkLiveData().setKeyIndex(keyIndex); - } - - @Override - public void onFlagsSelected(final int keyRefreshFlag, final int ivUpdateFlag) { - mViewModel.getMeshNetworkLiveData().setFlags(MeshParserUtils.parseUpdateFlags(keyRefreshFlag, ivUpdateFlag)); - } - - @Override - public void setIvIndex(final int ivIndex) { - mViewModel.getMeshNetworkLiveData().setIvIndex(ivIndex); + public boolean setUnicastAddress(final int unicastAddress) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + return network.assignUnicastAddress(unicastAddress); + } + return false; } @Override - public void setUnicastAddress(final int unicastAddress) { - mViewModel.getMeshNetworkLiveData().setUnicastAddress(unicastAddress); + public int getNextUnicastAddress(final int elementCount) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + return network.nextAvailableUnicastAddress(elementCount, network.getSelectedProvisioner()); } @Override @@ -334,7 +344,7 @@ public void onProvisioningFailed() { } private void disconnect() { - mViewModel.getUnProvisionedMeshNode().removeObservers(this); + mViewModel.getUnprovisionedMeshNode().removeObservers(this); mViewModel.disconnect(); } @@ -347,11 +357,11 @@ public void setupProvisionerStateObservers(final View provisioningStatusContaine recyclerView.setAdapter(adapter); mViewModel.getProvisioningStatus().observe(this, provisioningStateLiveData -> { - if(provisioningStateLiveData != null) { + if (provisioningStateLiveData != null) { final ProvisionerProgress provisionerProgress = provisioningStateLiveData.getProvisionerProgress(); adapter.refresh(provisioningStateLiveData.getStateList()); if (provisionerProgress != null) { - final ProvisioningStatusLiveData.ProvisioningLiveDataState state = provisionerProgress.getState(); + final ProvisionerStates state = provisionerProgress.getState(); switch (state) { case PROVISIONING_FAILED: if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_PROVISIONING_FAILED) == null) { @@ -363,21 +373,21 @@ public void setupProvisionerStateObservers(final View provisioningStatusContaine case PROVISIONING_AUTHENTICATION_STATIC_OOB_WAITING: if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_AUTH_INPUT_TAG) == null) { DialogFragmentAuthenticationInput dialogFragmentAuthenticationInput = DialogFragmentAuthenticationInput. - newInstance(mViewModel.getUnProvisionedMeshNode().getValue()); + newInstance(mViewModel.getUnprovisionedMeshNode().getValue()); dialogFragmentAuthenticationInput.show(getSupportFragmentManager(), DIALOG_FRAGMENT_AUTH_INPUT_TAG); } break; case PROVISIONING_AUTHENTICATION_OUTPUT_OOB_WAITING: if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_AUTH_INPUT_TAG) == null) { DialogFragmentAuthenticationInput dialogFragmentAuthenticationInput = DialogFragmentAuthenticationInput. - newInstance(mViewModel.getUnProvisionedMeshNode().getValue()); + newInstance(mViewModel.getUnprovisionedMeshNode().getValue()); dialogFragmentAuthenticationInput.show(getSupportFragmentManager(), DIALOG_FRAGMENT_AUTH_INPUT_TAG); } break; case PROVISIONING_AUTHENTICATION_INPUT_OOB_WAITING: if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_AUTH_INPUT_TAG) == null) { DialogFragmentAuthenticationInput dialogFragmentAuthenticationInput = DialogFragmentAuthenticationInput. - newInstance(mViewModel.getUnProvisionedMeshNode().getValue()); + newInstance(mViewModel.getUnprovisionedMeshNode().getValue()); dialogFragmentAuthenticationInput.show(getSupportFragmentManager(), DIALOG_FRAGMENT_AUTH_INPUT_TAG); } break; @@ -387,13 +397,18 @@ public void setupProvisionerStateObservers(final View provisioningStatusContaine if (fragment != null) fragment.dismiss(); break; - case APP_KEY_STATUS_RECEIVED: - if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_APP_KEY_STATUS) == null) { - DialogFragmentAppKeyAddStatus fragmentAppKeyAddStatus = DialogFragmentAppKeyAddStatus. + case NETWORK_TRANSMIT_STATUS_RECEIVED: + if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_CONFIGURATION_STATUS) == null) { + DialogFragmentConfigurationComplete fragmentConfigComplete = DialogFragmentConfigurationComplete. newInstance(getString(R.string.title_configuration_compete), getString(R.string.configuration_complete_summary)); - fragmentAppKeyAddStatus.show(getSupportFragmentManager(), DIALOG_FRAGMENT_APP_KEY_STATUS); + fragmentConfigComplete.show(getSupportFragmentManager(), DIALOG_FRAGMENT_CONFIGURATION_STATUS); } break; + case PROVISIONER_UNASSIGNED: + setResultIntent(); + break; + default: + break; } } @@ -404,7 +419,7 @@ public void setupProvisionerStateObservers(final View provisioningStatusContaine } @Override - public void onAppKeyAddStatusReceived() { + public void onConfigurationCompleted() { setResultIntent(); } @@ -413,10 +428,21 @@ private void setResultIntent() { if (mViewModel.isProvisioningComplete()) { returnIntent.putExtra(Utils.PROVISIONING_COMPLETED, true); setResult(Activity.RESULT_OK, returnIntent); - if (mViewModel.isCompositionDataStatusReceived()) { - returnIntent.putExtra(Utils.COMPOSITION_DATA_COMPLETED, true); - if (mViewModel.isAppKeyAddCompleted()) { - returnIntent.putExtra(Utils.APP_KEY_ADD_COMPLETED, true); + final ProvisionerProgress progress = mViewModel.getProvisioningStatus().getProvisionerProgress(); + if (progress.getState() == ProvisionerStates.PROVISIONER_UNASSIGNED) { + returnIntent.putExtra(Utils.PROVISIONER_UNASSIGNED, true); + } else { + if (mViewModel.isCompositionDataStatusReceived()) { + returnIntent.putExtra(Utils.COMPOSITION_DATA_COMPLETED, true); + if (mViewModel.isDefaultTtlReceived()) { + returnIntent.putExtra(Utils.DEFAULT_GET_COMPLETED, true); + if (mViewModel.isAppKeyAddCompleted()) { + returnIntent.putExtra(Utils.APP_KEY_ADD_COMPLETED, true); + if (mViewModel.isNetworkRetransmitSetCompleted()) { + returnIntent.putExtra(Utils.NETWORK_TRANSMIT_SET_COMPLETED, true); + } + } + } } } } @@ -517,41 +543,61 @@ private String parseInputOOBActions(final ProvisioningCapabilities capabilities) @Override public void onNoOOBSelected() { - final UnprovisionedMeshNode node = mViewModel.getUnProvisionedMeshNode().getValue(); + final UnprovisionedMeshNode node = mViewModel.getUnprovisionedMeshNode().getValue(); if (node != null) { - setupProvisionerStateObservers(provisioningStatusContainer); - mProvisioningProgressBar.setVisibility(View.VISIBLE); - mViewModel.getMeshManagerApi().startProvisioning(node); + try { + node.setNodeName(mViewModel.getNetworkLiveData().getNodeName()); + setupProvisionerStateObservers(provisioningStatusContainer); + mProvisioningProgressBar.setVisibility(View.VISIBLE); + mViewModel.getMeshManagerApi().startProvisioning(node); + } catch (IllegalArgumentException ex) { + mViewModel.displaySnackBar(this, mCoordinatorLayout, ex.getMessage(), Snackbar.LENGTH_LONG); + } } } @Override public void onStaticOOBSelected(final StaticOOBType staticOOBType) { - final UnprovisionedMeshNode node = mViewModel.getUnProvisionedMeshNode().getValue(); + final UnprovisionedMeshNode node = mViewModel.getUnprovisionedMeshNode().getValue(); if (node != null) { - setupProvisionerStateObservers(provisioningStatusContainer); - mProvisioningProgressBar.setVisibility(View.VISIBLE); - mViewModel.getMeshManagerApi().startProvisioningWithStaticOOB(node); + try { + node.setNodeName(mViewModel.getNetworkLiveData().getNodeName()); + setupProvisionerStateObservers(provisioningStatusContainer); + mProvisioningProgressBar.setVisibility(View.VISIBLE); + mViewModel.getMeshManagerApi().startProvisioningWithStaticOOB(node); + } catch (IllegalArgumentException ex) { + mViewModel.displaySnackBar(this, mCoordinatorLayout, ex.getMessage(), Snackbar.LENGTH_LONG); + } } } @Override public void onOutputOOBActionSelected(final OutputOOBAction action) { - final UnprovisionedMeshNode node = mViewModel.getUnProvisionedMeshNode().getValue(); + final UnprovisionedMeshNode node = mViewModel.getUnprovisionedMeshNode().getValue(); if (node != null) { - setupProvisionerStateObservers(provisioningStatusContainer); - mProvisioningProgressBar.setVisibility(View.VISIBLE); - mViewModel.getMeshManagerApi().startProvisioningWithOutputOOB(node, action); + try { + node.setNodeName(mViewModel.getNetworkLiveData().getNodeName()); + setupProvisionerStateObservers(provisioningStatusContainer); + mProvisioningProgressBar.setVisibility(View.VISIBLE); + mViewModel.getMeshManagerApi().startProvisioningWithOutputOOB(node, action); + } catch (IllegalArgumentException ex) { + mViewModel.displaySnackBar(this, mCoordinatorLayout, ex.getMessage(), Snackbar.LENGTH_LONG); + } } } @Override public void onInputOOBActionSelected(final InputOOBAction action) { - final UnprovisionedMeshNode node = mViewModel.getUnProvisionedMeshNode().getValue(); + final UnprovisionedMeshNode node = mViewModel.getUnprovisionedMeshNode().getValue(); if (node != null) { - setupProvisionerStateObservers(provisioningStatusContainer); - mProvisioningProgressBar.setVisibility(View.VISIBLE); - mViewModel.getMeshManagerApi().startProvisioningWithInputOOB(node, action); + try { + node.setNodeName(mViewModel.getNetworkLiveData().getNodeName()); + setupProvisionerStateObservers(provisioningStatusContainer); + mProvisioningProgressBar.setVisibility(View.VISIBLE); + mViewModel.getMeshManagerApi().startProvisioningWithInputOOB(node, action); + } catch (IllegalArgumentException ex) { + mViewModel.displaySnackBar(this, mCoordinatorLayout, ex.getMessage(), Snackbar.LENGTH_LONG); + } } } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ProxyFilterFragment.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ProxyFilterFragment.java new file mode 100644 index 000000000..e75dfeeba --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ProxyFilterFragment.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.cardview.widget.CardView; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; +import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigAddAddressToFilter; +import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigRemoveAddressFromFilter; +import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigSetFilterType; +import no.nordicsemi.android.meshprovisioner.utils.AddressArray; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.meshprovisioner.utils.ProxyFilter; +import no.nordicsemi.android.meshprovisioner.utils.ProxyFilterType; +import no.nordicsemi.android.nrfmeshprovisioner.adapter.FilterAddressAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentError; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentFilterAddAddress; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.SharedViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; + +public class ProxyFilterFragment extends Fragment implements Injectable, + DialogFragmentFilterAddAddress.DialogFragmentFilterAddressListener, + ItemTouchHelperAdapter { + + private static final String CLEAR_ADDRESS_PRESSED = "CLEAR_ADDRESS_PRESSED"; + private static final String PROXY_FILTER_DISABLED = "PROXY_FILTER_DISABLED"; + + private SharedViewModel mViewModel; + + @Inject + ViewModelProvider.Factory mViewModelFactory; + @BindView(R.id.action_white_list) + Button actionEnableWhiteList; + @BindView(R.id.action_black_list) + Button actionEnableBlackList; + @BindView(R.id.action_disable) + Button actionDisable; + @BindView(R.id.action_add_address) + Button actionAddFilterAddress; + @BindView(R.id.action_clear_addresses) + Button actionClearFilterAddress; + @BindView(R.id.proxy_filter_address_card) + CardView mProxyFilterCard; + private ProxyFilter mFilter; + private boolean clearAddressPressed; + private boolean isProxyFilterDisabled; + private FilterAddressAdapter addressAdapter; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Nullable + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = inflater.inflate(R.layout.fragment_proxy_filter, null); + mViewModel = ViewModelProviders.of(requireActivity(), mViewModelFactory).get(SharedViewModel.class); + ButterKnife.bind(this, rootView); + + if (savedInstanceState != null) { + clearAddressPressed = savedInstanceState.getBoolean(CLEAR_ADDRESS_PRESSED); + isProxyFilterDisabled = savedInstanceState.getBoolean(PROXY_FILTER_DISABLED); + } + + final TextView noAddressesAdded = rootView.findViewById(R.id.no_addresses); + final RecyclerView recyclerViewAddresses = rootView.findViewById(R.id.recycler_view_filter_addresses); + actionEnableWhiteList.setEnabled(false); + actionEnableBlackList.setEnabled(false); + actionDisable.setEnabled(false); + + recyclerViewAddresses.setLayoutManager(new LinearLayoutManager(requireContext())); + recyclerViewAddresses.setItemAnimator(new DefaultItemAnimator()); + final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); + final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); + itemTouchHelper.attachToRecyclerView(recyclerViewAddresses); + addressAdapter = new FilterAddressAdapter(requireContext()); + recyclerViewAddresses.setAdapter(addressAdapter); + + mViewModel.isConnectedToProxy().observe(this, isConnected -> { + if (!isConnected) { + clearAddressPressed = false; + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + mFilter = network.getProxyFilter(); + if (mFilter == null) { + addressAdapter.clearData(); + noAddressesAdded.setVisibility(View.VISIBLE); + recyclerViewAddresses.setVisibility(View.GONE); + } + } + + actionEnableWhiteList.setSelected(isConnected); + actionEnableBlackList.setSelected(isConnected); + actionDisable.setSelected(isConnected); + actionAddFilterAddress.setEnabled(isConnected); + actionClearFilterAddress.setVisibility(View.GONE); + } + actionDisable.setEnabled(false); + actionEnableWhiteList.setEnabled(isConnected); + actionEnableBlackList.setEnabled(isConnected); + }); + + mViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { + final MeshNetwork network = meshNetworkLiveData.getMeshNetwork(); + if (network == null) { + return; + } + + final ProxyFilter filter = mFilter = network.getProxyFilter(); + if (filter == null) { + addressAdapter.clearData(); + return; + } else if (clearAddressPressed) { + clearAddressPressed = false; + return; + } else if (isProxyFilterDisabled) { + actionDisable.setSelected(true); + } + + actionEnableWhiteList.setSelected(mFilter.getFilterType().getType() == ProxyFilterType.WHITE_LIST_FILTER && !actionDisable.isSelected()); + actionEnableBlackList.setSelected(mFilter.getFilterType().getType() == ProxyFilterType.BLACK_LIST_FILTER); + + if (!mFilter.getAddresses().isEmpty()) { + noAddressesAdded.setVisibility(View.GONE); + actionClearFilterAddress.setVisibility(View.VISIBLE); + } else { + noAddressesAdded.setVisibility(View.VISIBLE); + actionClearFilterAddress.setVisibility(View.GONE); + } + actionAddFilterAddress.setEnabled(!actionDisable.isSelected()); + addressAdapter.updateData(filter); + }); + + actionEnableWhiteList.setOnClickListener(v -> { + isProxyFilterDisabled = false; + v.setSelected(true); + actionEnableBlackList.setSelected(false); + actionDisable.setSelected(false); + actionDisable.setEnabled(true); + setFilter(new ProxyFilterType(ProxyFilterType.WHITE_LIST_FILTER)); + }); + + actionEnableBlackList.setOnClickListener(v -> { + isProxyFilterDisabled = false; + v.setSelected(true); + actionEnableWhiteList.setSelected(false); + actionDisable.setSelected(false); + actionDisable.setEnabled(true); + setFilter(new ProxyFilterType(ProxyFilterType.BLACK_LIST_FILTER)); + }); + + actionDisable.setOnClickListener(v -> { + v.setSelected(true); + isProxyFilterDisabled = true; + actionEnableWhiteList.setSelected(false); + actionEnableBlackList.setSelected(false); + addressAdapter.clearData(); + actionDisable.setEnabled(false); + setFilter(new ProxyFilterType(ProxyFilterType.WHITE_LIST_FILTER)); + }); + + actionAddFilterAddress.setOnClickListener(v -> { + final ProxyFilterType filterType; + if (mFilter == null) { + filterType = new ProxyFilterType(ProxyFilterType.WHITE_LIST_FILTER); + } else { + filterType = mFilter.getFilterType(); + } + final DialogFragmentFilterAddAddress filterAddAddress = DialogFragmentFilterAddAddress.newInstance(filterType); + filterAddAddress.show(getChildFragmentManager(), null); + }); + + actionClearFilterAddress.setOnClickListener(v -> removeAddresses()); + + return rootView; + } + + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(CLEAR_ADDRESS_PRESSED, clearAddressPressed); + outState.putBoolean(PROXY_FILTER_DISABLED, isProxyFilterDisabled); + } + + @Override + public void addAddresses(final List addresses) { + final ProxyConfigAddAddressToFilter addAddressToFilter = new ProxyConfigAddAddressToFilter(addresses); + sendMessage(addAddressToFilter); + } + + @Override + public void onItemDismiss(final RemovableViewHolder viewHolder) { + final int position = viewHolder.getAdapterPosition(); + if (viewHolder instanceof FilterAddressAdapter.ViewHolder) { + removeAddress(position); + } + } + + @Override + public void onItemDismissFailed(final RemovableViewHolder viewHolder) { + + } + + private void removeAddress(final int position) { + final MeshNetwork meshNetwork = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (meshNetwork != null) { + final ProxyFilter proxyFilter = meshNetwork.getProxyFilter(); + if (proxyFilter != null) { + clearAddressPressed = true; + final AddressArray addressArr = proxyFilter.getAddresses().get(position); + final List addresses = new ArrayList<>(); + addresses.add(addressArr); + addressAdapter.clearRow(position); + final ProxyConfigRemoveAddressFromFilter removeAddressFromFilter = new ProxyConfigRemoveAddressFromFilter(addresses); + sendMessage(removeAddressFromFilter); + } + } + } + + private void removeAddresses() { + final MeshNetwork meshNetwork = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (meshNetwork != null) { + final ProxyFilter proxyFilter = meshNetwork.getProxyFilter(); + if (proxyFilter != null) { + if (!proxyFilter.getAddresses().isEmpty()) { + final ProxyConfigRemoveAddressFromFilter removeAddressFromFilter = new ProxyConfigRemoveAddressFromFilter(proxyFilter.getAddresses()); + sendMessage(removeAddressFromFilter); + } + } + } + } + + private void setFilter(final ProxyFilterType filterType) { + final ProxyConfigSetFilterType setFilterType = new ProxyConfigSetFilterType(filterType); + sendMessage(setFilterType); + } + + private void sendMessage(final MeshMessage meshMessage) { + try { + mViewModel.getMeshManagerApi().createMeshPdu(MeshAddress.UNASSIGNED_ADDRESS, meshMessage); + } catch (IllegalArgumentException ex) { + final DialogFragmentError message = DialogFragmentError. + newInstance(getString(R.string.title_error), ex.getMessage()); + message.show(getChildFragmentManager(), null); + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/PublicationSettingsActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/PublicationSettingsActivity.java deleted file mode 100644 index 8e77acce1..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/PublicationSettingsActivity.java +++ /dev/null @@ -1,356 +0,0 @@ -package no.nordicsemi.android.nrfmeshprovisioner; - -import android.app.Activity; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Switch; -import android.widget.TextView; - -import javax.inject.Inject; - -import butterknife.BindView; -import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; -import no.nordicsemi.android.meshprovisioner.transport.MeshModel; -import no.nordicsemi.android.meshprovisioner.utils.AddressUtils; -import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; -import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.meshprovisioner.utils.PublicationSettings; -import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentPubRetransmitIntervalSteps; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentPublicationResolution; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentPublicationSteps; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentPublishAddress; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentPublishTtl; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentRetransmitCount; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.PublicationViewModel; - -public class PublicationSettingsActivity extends AppCompatActivity implements Injectable, - DialogFragmentPublishAddress.DialogFragmentPublishAddressListener, - DialogFragmentPublicationSteps.DialogFragmentPublicationStepsListener, - DialogFragmentPublicationResolution.DialogFragmentPublicationResolutionListener, - DialogFragmentRetransmitCount.DialogFragmentRetransmitCountListener, - DialogFragmentPubRetransmitIntervalSteps.DialogFragmentIntervalStepsListener, - DialogFragmentPublishTtl.DialogFragmentPublishTtlListener { - - public static final int SET_PUBLICATION_SETTINGS = 2021; - public static final String RESULT_PUBLISH_ADDRESS = "RESULT_PUBLISH_ADDRESS"; - public static final String RESULT_APP_KEY_INDEX = "RESULT_APP_KEY_INDEX"; - public static final String RESULT_CREDENTIAL_FLAG = "RESULT_CREDENTIAL_FLAG"; - public static final String RESULT_PUBLISH_TTL = "RESULT_PUBLISH_TTL"; - public static final String RESULT_PUBLICATION_STEPS = "RESULT_PUBLICATION_STEPS"; - public static final String RESULT_PUBLICATION_RESOLUTION = "RESULT_PUBLICATION_RESOLUTION"; - public static final String RESULT_PUBLISH_RETRANSMIT_COUNT = "RESULT_PUBLISH_RETRANSMIT_COUNT"; - public static final String RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS = "RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS"; - - private static final int DEFAULT_PUB_RETRANSMIT_COUNT = 1; - private static final int DEFAULT_PUB_RETRANSMIT_INTERVAL_STEPS = 1; - private static final int DEFAULT_PUBLICATION_STEPS = 0; - @SuppressWarnings("unused") - private static final int DEFAULT_PUBLICATION_RESOLUTION = MeshParserUtils.RESOLUTION_100_MS; - - private MeshModel mMeshModel; - private int mPublishAddress; - private Integer mAppKeyIndex; - private int mPublishTtl = MeshParserUtils.USE_DEFAULT_TTL; - private int mPublicationSteps = DEFAULT_PUBLICATION_STEPS; - private int mPublicationResolution; - private int mPublishRetransmitCount = DEFAULT_PUB_RETRANSMIT_COUNT; - private int mPublishRetransmitIntervalSteps = DEFAULT_PUB_RETRANSMIT_INTERVAL_STEPS; - - @Inject - ViewModelProvider.Factory mViewModelFactory; - - @BindView(R.id.publish_address) - TextView mPublishAddressView; - @BindView(R.id.retransmit_count) - TextView mRetransmitCountView; - @BindView(R.id.interval_steps) - TextView mIntervalStepsView; - @BindView(R.id.publication_steps) - TextView mPublicationStepsView; - @BindView(R.id.publication_resolution) - TextView mPublicationResolutionView; - @BindView(R.id.app_key_index) - TextView mAppKeyIndexView; - @BindView(R.id.publication_ttl) - TextView mPublishTtlView; - @BindView(R.id.friendship_credential_flag) - Switch mActionFriendshipCredentialSwitch; - - @Override - protected void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_publication_settings); - ButterKnife.bind(this); - - final PublicationViewModel viewModel = ViewModelProviders.of(this, mViewModelFactory).get(PublicationViewModel.class); - - final MeshModel meshModel = mMeshModel = viewModel.getSelectedModel().getValue(); - if (meshModel == null) - finish(); - - //Setup views - final Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - //noinspection ConstantConditions - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.title_publication_settings); - - final View actionPublishAddress = findViewById(R.id.container_publish_address); - final View actionRetransmitCount = findViewById(R.id.container_retransmission_count); - final View actionIntervalSteps = findViewById(R.id.container_interval_steps); - final View actionPublicationSteps = findViewById(R.id.container_publish_steps); - final View actionResolution = findViewById(R.id.container_resolution); - final View actionKeyIndex = findViewById(R.id.container_app_key_index); - final View actionPublishTtl = findViewById(R.id.container_publication_ttl); - - if (savedInstanceState == null) { - //noinspection ConstantConditions - updateUi(meshModel); - } - - actionPublishAddress.setOnClickListener(v -> { - final byte[] address = AddressUtils.getUnicastAddressBytes(this.mPublishAddress); - final DialogFragmentPublishAddress fragmentPublishAddress = DialogFragmentPublishAddress.newInstance(address); - fragmentPublishAddress.show(getSupportFragmentManager(), null); - }); - - actionRetransmitCount.setOnClickListener(v -> { - final DialogFragmentRetransmitCount fragmentRetransmitCount = DialogFragmentRetransmitCount.newInstance(mPublishRetransmitCount); - fragmentRetransmitCount.show(getSupportFragmentManager(), null); - }); - - actionIntervalSteps.setOnClickListener(v -> { - final DialogFragmentPubRetransmitIntervalSteps fragmentIntervalSteps = DialogFragmentPubRetransmitIntervalSteps.newInstance(mPublishRetransmitIntervalSteps); - fragmentIntervalSteps.show(getSupportFragmentManager(), null); - }); - - actionPublicationSteps.setOnClickListener(v -> { - final DialogFragmentPublicationSteps fragmentPublicationSteps = DialogFragmentPublicationSteps.newInstance(mPublicationSteps); - fragmentPublicationSteps.show(getSupportFragmentManager(), null); - }); - - actionResolution.setOnClickListener(v -> { - final DialogFragmentPublicationResolution fragmentPublicationResolution = DialogFragmentPublicationResolution.newInstance(mPublicationResolution); - fragmentPublicationResolution.show(getSupportFragmentManager(), null); - }); - - actionKeyIndex.setOnClickListener(v -> { - final Intent bindAppKeysIntent = new Intent(this, ManageAppKeysActivity.class); - bindAppKeysIntent.putExtra(Utils.EXTRA_DATA, Utils.PUBLICATION_APP_KEY); - startActivityForResult(bindAppKeysIntent, ManageAppKeysActivity.SELECT_APP_KEY); - }); - - actionPublishTtl.setOnClickListener(v -> { - if(meshModel != null) { - final PublicationSettings publicationSettings = meshModel.getPublicationSettings(); - final DialogFragmentPublishTtl fragmentPublishTtl; - if (publicationSettings != null) { - fragmentPublishTtl = DialogFragmentPublishTtl.newInstance(meshModel.getPublicationSettings().getPublishTtl()); - } else { - fragmentPublishTtl = DialogFragmentPublishTtl.newInstance(MeshParserUtils.USE_DEFAULT_TTL); - } - fragmentPublishTtl.show(getSupportFragmentManager(), null); - } - }); - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - getMenuInflater().inflate(R.menu.publication_apply, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; - case R.id.action_apply: - setReturnIntent(); - return true; - } - return false; - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - } - - @Override - protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == ManageAppKeysActivity.SELECT_APP_KEY) { - if (resultCode == RESULT_OK) { - final ApplicationKey appKey = data.getParcelableExtra(ManageAppKeysActivity.RESULT_APP_KEY); - if (appKey != null) { - mAppKeyIndex = appKey.getKeyIndex(); - mAppKeyIndexView.setText(getString(R.string.app_key_index, appKey.getKeyIndex())); - } - } - } - } - - @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(RESULT_PUBLISH_ADDRESS, mPublishAddress); - outState.putInt(RESULT_APP_KEY_INDEX, mAppKeyIndex); - outState.putBoolean(RESULT_CREDENTIAL_FLAG, mActionFriendshipCredentialSwitch.isChecked()); - outState.putInt(RESULT_PUBLISH_TTL, mPublishTtl); - outState.putInt(RESULT_PUBLICATION_STEPS, mPublicationSteps); - outState.putInt(RESULT_PUBLICATION_RESOLUTION, mPublicationResolution); - outState.putInt(RESULT_PUBLISH_RETRANSMIT_COUNT, mPublishRetransmitCount); - outState.putInt(RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS, mPublishRetransmitIntervalSteps); - } - - @Override - protected void onRestoreInstanceState(final Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mPublishAddress = savedInstanceState.getInt(RESULT_PUBLISH_ADDRESS); - mAppKeyIndex = savedInstanceState.getInt(RESULT_APP_KEY_INDEX); - mPublishTtl = savedInstanceState.getInt(RESULT_PUBLISH_TTL); - mPublicationSteps = savedInstanceState.getInt(RESULT_PUBLICATION_STEPS); - mPublicationResolution = savedInstanceState.getInt(RESULT_PUBLICATION_RESOLUTION); - mPublishRetransmitCount = savedInstanceState.getInt(RESULT_PUBLISH_RETRANSMIT_COUNT); - mPublishRetransmitIntervalSteps = savedInstanceState.getInt(RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS); - updateUi(savedInstanceState.getBoolean(RESULT_CREDENTIAL_FLAG)); - } - - @Override - public void setPublishAddress(final byte[] publishAddress) { - if (publishAddress != null) { - mPublishAddress = MeshParserUtils.unsignedBytesToInt(publishAddress[1], publishAddress[0]); - mPublishAddressView.setText(MeshParserUtils.bytesToHex(publishAddress, true)); - } - } - - @Override - public void setPublicationResolution(final int resolution) { - mPublicationResolution = resolution; - mPublicationResolutionView.setText(getResolutionSummary(resolution)); - } - - @Override - public void setPublicationSteps(final int publicationSteps) { - mPublicationSteps = publicationSteps; - mPublicationStepsView.setText(getString(R.string.publication_steps, publicationSteps)); - } - - @Override - public void setRetransmitCount(final int retransmitCount) { - mPublishRetransmitCount = retransmitCount; - mRetransmitCountView.setText(getString(R.string.retransmit_count, retransmitCount)); - } - - @Override - public void setRetransmitIntervalSteps(final int intervalSteps) { - mPublishRetransmitIntervalSteps = intervalSteps; - mIntervalStepsView.setText(getString(R.string.retransmit_interval_steps, intervalSteps)); - } - - @Override - public void setPublishTtl(final int ttl) { - mPublishTtl = ttl; - updateTtlUi(ttl); - } - - private void updateUi(@NonNull final MeshModel model) { - final PublicationSettings publicationSettings = model.getPublicationSettings(); - - //Default app key index to the 0th key in the list of bound app keys - if (!model.getBoundAppKeyIndexes().isEmpty()) { - mAppKeyIndex = mMeshModel.getBoundAppKeyIndexes().get(0); - } - - if (publicationSettings != null) { - mPublishAddress = publicationSettings.getPublishAddress(); - mPublishAddressView.setText(MeshAddress.formatAddress(mPublishAddress, true)); - - mActionFriendshipCredentialSwitch.setChecked(publicationSettings.getCredentialFlag()); - mPublishTtl = publicationSettings.getPublishTtl(); - - mPublicationSteps = publicationSettings.getPublicationSteps(); - mPublicationStepsView.setText(getString(R.string.publication_steps, mPublicationSteps)); - - mPublicationResolution = publicationSettings.getPublicationResolution(); - mPublicationResolutionView.setText(getResolutionSummary(mPublicationResolution)); - - mPublishRetransmitCount = publicationSettings.getPublishRetransmitCount(); - mRetransmitCountView.setText(getString(R.string.retransmit_count, mPublishRetransmitCount)); - - mPublishRetransmitIntervalSteps = publicationSettings.getPublishRetransmitIntervalSteps(); - mIntervalStepsView.setText(getString(R.string.retransmit_interval_steps, mPublishRetransmitIntervalSteps)); - - if (!model.getBoundAppKeyIndexes().isEmpty()) { - mAppKeyIndex = publicationSettings.getAppKeyIndex(); - } - updateUi(publicationSettings.getCredentialFlag()); - } - final int ttl = mPublishTtl; - updateTtlUi(ttl); - - } - - private void updateUi(final boolean credentialFlag) { - mPublishAddressView.setText(MeshAddress.formatAddress(mPublishAddress, true)); - mAppKeyIndexView.setText(getString(R.string.app_key_index, mAppKeyIndex)); - mActionFriendshipCredentialSwitch.setChecked(credentialFlag); - updateTtlUi(mPublishTtl); - mPublicationStepsView.setText(getString(R.string.publication_steps, mPublicationSteps)); - mPublicationResolutionView.setText(getResolutionSummary(mPublicationResolution)); - mRetransmitCountView.setText(getString(R.string.retransmit_count, mPublishRetransmitCount)); - mIntervalStepsView.setText(getString(R.string.retransmit_interval_steps, mPublishRetransmitIntervalSteps)); - - } - - private void updateTtlUi(final int ttl) { - if (MeshParserUtils.isDefaultPublishTtl(ttl)) { - mPublishTtlView.setText(getString(R.string.uses_default_ttl)); - } else { - mPublishTtlView.setText(String.valueOf(ttl)); - } - } - - private String getResolutionSummary(final int resolution) { - switch (resolution) { - default: - case MeshParserUtils.RESOLUTION_100_MS: - return getString(R.string.resolution_summary_100_ms); - case MeshParserUtils.RESOLUTION_1_S: - return getString(R.string.resolution_summary_1_s); - case MeshParserUtils.RESOLUTION_10_S: - return getString(R.string.resolution_summary_10_s); - case MeshParserUtils.RESOLUTION_10_M: - return getString(R.string.resolution_summary_100_m); - } - - } - - private void setReturnIntent() { - Intent returnIntent = new Intent(); - returnIntent.putExtra(RESULT_PUBLISH_ADDRESS, mPublishAddress); - returnIntent.putExtra(RESULT_APP_KEY_INDEX, mAppKeyIndex); - returnIntent.putExtra(RESULT_CREDENTIAL_FLAG, mActionFriendshipCredentialSwitch.isChecked()); - returnIntent.putExtra(RESULT_PUBLISH_TTL, mPublishTtl); - returnIntent.putExtra(RESULT_PUBLICATION_STEPS, mPublicationSteps); - returnIntent.putExtra(RESULT_PUBLICATION_RESOLUTION, mPublicationResolution); - returnIntent.putExtra(RESULT_PUBLISH_RETRANSMIT_COUNT, mPublishRetransmitCount); - returnIntent.putExtra(RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS, mPublishRetransmitIntervalSteps); - setResult(Activity.RESULT_OK, returnIntent); - finish(); - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/SettingsFragment.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/SettingsFragment.java index e139b6535..9fb552d00 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/SettingsFragment.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/SettingsFragment.java @@ -24,16 +24,10 @@ import android.Manifest; import android.annotation.SuppressLint; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -45,48 +39,36 @@ import android.widget.Toast; import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; import javax.inject.Inject; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; -import no.nordicsemi.android.meshprovisioner.transport.NetworkKey; -import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentFlags; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentGlobalNetworkName; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentGlobalTtl; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentIvIndex; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentKeyIndex; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentMeshExportMsg; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentMeshImport; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentMeshImportMsg; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentNetworkKey; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentNetworkName; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentPermissionRationale; import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentResetNetwork; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentSourceAddress; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentUnicastAddress; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.NetKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.ProvisionersActivity; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.SharedViewModel; import static android.app.Activity.RESULT_OK; -import static no.nordicsemi.android.nrfmeshprovisioner.ManageAppKeysActivity.RESULT_APP_KEY_LIST_SIZE; import static no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NrfMeshRepository.EXPORT_PATH; public class SettingsFragment extends Fragment implements Injectable, - DialogFragmentGlobalNetworkName.DialogFragmentNetworkNameListener, - DialogFragmentGlobalTtl.DialogFragmentGlobalTtlListener, - DialogFragmentNetworkKey.DialogFragmentNetworkKeyListener, - DialogFragmentKeyIndex.DialogFragmentKeyIndexListener, - DialogFragmentFlags.DialogFragmentFlagsListener, - DialogFragmentIvIndex.DialogFragmentIvIndexListener, - DialogFragmentUnicastAddress.DialogFragmentUnicastAddressListener, - DialogFragmentSourceAddress.DialogFragmentSourceAddressListener, + DialogFragmentNetworkName.DialogFragmentNetworkNameListener, DialogFragmentResetNetwork.DialogFragmentResetNetworkListener, DialogFragmentMeshImport.DialogFragmentNetworkImportListener, -DialogFragmentPermissionRationale.StoragePermissionListener { + DialogFragmentPermissionRationale.StoragePermissionListener { private static final int REQUEST_STORAGE_PERMISSION = 2023; // random number private static final int READ_FILE_REQUEST_CODE = 42; @@ -96,7 +78,6 @@ public class SettingsFragment extends Fragment implements Injectable, @Inject ViewModelProvider.Factory mViewModelFactory; - private TextView manageAppKeysView; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -107,135 +88,81 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @SuppressWarnings("ConstantConditions") @Nullable @Override - public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { @SuppressLint("InflateParams") final View rootView = inflater.inflate(R.layout.fragment_settings, null); - mViewModel = ViewModelProviders.of(getActivity(), mViewModelFactory).get(SharedViewModel.class); // Set up views final View containerNetworkName = rootView.findViewById(R.id.container_network_name); - containerNetworkName.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_lan_black_alpha_24dp)); + containerNetworkName.findViewById(R.id.image) + .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_label_black_alpha_24dp)); final TextView networkNameTitle = containerNetworkName.findViewById(R.id.title); - networkNameTitle.setText(R.string.summary_global_network_name); + networkNameTitle.setText(R.string.title_network_name); final TextView networkNameView = containerNetworkName.findViewById(R.id.text); + networkNameView.setVisibility(View.VISIBLE); containerNetworkName.setOnClickListener(v -> { - final DialogFragmentGlobalNetworkName dialogFragmentNetworkKey = DialogFragmentGlobalNetworkName.newInstance(networkNameView.getText().toString()); - dialogFragmentNetworkKey.show(getChildFragmentManager(), null); - }); - - final View containerGlobalTtl = rootView.findViewById(R.id.container_global_ttl); - containerGlobalTtl.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_timer)); - final TextView globalTtlTitle = containerGlobalTtl.findViewById(R.id.title); - globalTtlTitle.setText(R.string.summary_global_ttl); - final TextView globalTtlView = containerGlobalTtl.findViewById(R.id.text); - containerGlobalTtl.setOnClickListener(v -> { - final DialogFragmentGlobalTtl dialogFragmentGlobalTtl = DialogFragmentGlobalTtl.newInstance(globalTtlView.getText().toString()); - dialogFragmentGlobalTtl.show(getChildFragmentManager(), null); - }); - - final View containerSourceAddress = rootView.findViewById(R.id.container_src_address); - containerSourceAddress.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_lan_black_alpha_24dp)); - final TextView containerSource = containerSourceAddress.findViewById(R.id.title); - containerSource.setText(R.string.summary_src_address); - final TextView sourceAddressView = containerSourceAddress.findViewById(R.id.text); - containerSourceAddress.setOnClickListener(v -> { - final byte[] configuratorSrc = mViewModel.getMeshNetworkLiveData().getProvisionerAddress(); - final int src = (configuratorSrc[0] & 0xFF) << 8 | (configuratorSrc[1] & 0xFF); - final DialogFragmentSourceAddress dialogFragmentSrc = DialogFragmentSourceAddress.newInstance(src); - dialogFragmentSrc.show(getChildFragmentManager(), null); - }); - - final View containerKey = rootView.findViewById(R.id.container_key); - containerKey.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_vpn_key_black_alpha_24dp)); - final TextView keyTitle = containerKey.findViewById(R.id.title); - keyTitle.setText(R.string.summary_key); - final TextView keyView = containerKey.findViewById(R.id.text); - containerKey.setOnClickListener(v -> { - final NetworkKey networkKey = mViewModel.getMeshNetworkLiveData().getPrimaryNetworkKey(); - final DialogFragmentNetworkKey dialogFragmentNetworkKey = DialogFragmentNetworkKey.newInstance(networkKey); - dialogFragmentNetworkKey.show(getChildFragmentManager(), null); - }); - - final View containerKeyIndex = rootView.findViewById(R.id.container_index); - containerKeyIndex.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_numeric)); - final TextView keyIndexTitle = containerKeyIndex.findViewById(R.id.title); - keyIndexTitle.setText(R.string.summary_index); - final TextView keyIndexView = containerKeyIndex.findViewById(R.id.text); - containerKeyIndex.setOnClickListener(v -> { - final int keyIndex = mViewModel.getMeshNetworkLiveData().getKeyIndex(); - final DialogFragmentKeyIndex dialogFragmentNetworkKey = DialogFragmentKeyIndex.newInstance(keyIndex); - dialogFragmentNetworkKey.show(getChildFragmentManager(), null); - }); - - final View containerFlags = rootView.findViewById(R.id.container_flags); - containerFlags.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_flag)); - final TextView flagsTitle = containerFlags.findViewById(R.id.title); - flagsTitle.setText(R.string.summary_flags); - final TextView flagsView = containerFlags.findViewById(R.id.text); - containerFlags.setOnClickListener(v -> { - final int flags = mViewModel.getMeshNetworkLiveData().getFlags(); - final int keyRefreshFlag = MeshParserUtils.getBitValue(flags, 0); - final int ivUpdateFlag = MeshParserUtils.getBitValue(flags, 1); - final DialogFragmentFlags dialogFragmentFlags = DialogFragmentFlags.newInstance(keyRefreshFlag, ivUpdateFlag); - dialogFragmentFlags.show(getChildFragmentManager(), null); + final DialogFragmentNetworkName fragment = DialogFragmentNetworkName. + newInstance(networkNameView.getText().toString()); + fragment.show(getChildFragmentManager(), null); }); - final View containerIVIndex = rootView.findViewById(R.id.container_iv_index); - containerIVIndex.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_list)); - final TextView ivIndexTitle = containerIVIndex.findViewById(R.id.title); - ivIndexTitle.setText(R.string.title_iv_index); - final TextView ivIndexView = containerIVIndex.findViewById(R.id.text); - containerIVIndex.setOnClickListener(v -> { - final int ivIndex = mViewModel.getMeshNetworkLiveData().getIvIndex(); - final DialogFragmentIvIndex dialogFragmentFlags = DialogFragmentIvIndex.newInstance(ivIndex); - dialogFragmentFlags.show(getChildFragmentManager(), null); + final View containerProvisioner = rootView.findViewById(R.id.container_provisioners); + containerProvisioner.findViewById(R.id.image) + .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_folder_provisioner_black_alpha_24dp)); + final TextView provisionerTitle = containerProvisioner.findViewById(R.id.title); + final TextView provisionerSummary = containerProvisioner.findViewById(R.id.text); + provisionerSummary.setVisibility(View.VISIBLE); + provisionerTitle.setText(R.string.title_provisioners); + containerProvisioner.setOnClickListener(v -> { + final Intent intent = new Intent(requireContext(), ProvisionersActivity.class); + startActivity(intent); }); - final View containerUnicastAddress = rootView.findViewById(R.id.container_supported_algorithm); - containerUnicastAddress.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_lan_black_alpha_24dp)); - final TextView unicastAddressTitle = containerUnicastAddress.findViewById(R.id.title); - unicastAddressTitle.setText(R.string.summary_unicast_address); - final TextView unicastAddressView = containerUnicastAddress.findViewById(R.id.text); - containerUnicastAddress.setOnClickListener(v -> { - final int unicastAddress = mViewModel.getMeshNetworkLiveData().getValue().getUnicastAddress(); - final DialogFragmentUnicastAddress dialogFragmentFlags = DialogFragmentUnicastAddress.newInstance(unicastAddress); - dialogFragmentFlags.show(getChildFragmentManager(), null); + final View containerNetKey = rootView.findViewById(R.id.container_net_keys); + containerNetKey.findViewById(R.id.image) + .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_folder_key_black_24dp_alpha)); + final TextView keyTitle = containerNetKey.findViewById(R.id.title); + keyTitle.setText(R.string.title_net_keys); + final TextView netKeySummary = containerNetKey.findViewById(R.id.text); + netKeySummary.setVisibility(View.VISIBLE); + containerNetKey.setOnClickListener(v -> { + final Intent intent = new Intent(requireContext(), NetKeysActivity.class); + intent.putExtra(Utils.EXTRA_DATA, Utils.MANAGE_NET_KEY); + startActivity(intent); }); - final View containerManageAppKeys = rootView.findViewById(R.id.container_app_keys); - containerManageAppKeys.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_folder_key_black_24dp_alpha)); - final TextView manageAppKeys = containerManageAppKeys.findViewById(R.id.title); - manageAppKeys.setText(R.string.summary_app_keys); - manageAppKeysView = containerManageAppKeys.findViewById(R.id.text); - containerManageAppKeys.setOnClickListener(v -> { - final Intent intent = new Intent(getActivity(), ManageAppKeysActivity.class); - intent.putExtra(Utils.EXTRA_DATA, Utils.MANAGE_APP_KEY); - startActivityForResult(intent, ManageAppKeysActivity.MANAGE_APP_KEYS); + final View containerAppKey = rootView.findViewById(R.id.container_app_keys); + containerAppKey.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_folder_key_black_24dp_alpha)); + ((TextView) containerAppKey.findViewById(R.id.title)).setText(R.string.title_app_keys); + final TextView appKeySummary = containerAppKey.findViewById(R.id.text); + appKeySummary.setVisibility(View.VISIBLE); + containerAppKey.setOnClickListener(v -> { + final Intent intent = new Intent(requireContext(), AppKeysActivity.class); + startActivity(intent); }); final View containerAbout = rootView.findViewById(R.id.container_version); - containerAbout.findViewById(R.id.image).setBackground(ContextCompat.getDrawable(getContext(), R.drawable.ic_puzzle)); + containerAbout.setClickable(false); + containerAbout.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_puzzle)); final TextView versionTitle = containerAbout.findViewById(R.id.title); - versionTitle.setText(R.string.summary_verion); + versionTitle.setText(R.string.summary_version); final TextView version = containerAbout.findViewById(R.id.text); + version.setVisibility(View.VISIBLE); try { - version.setText(getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), 0).versionName); + version.setText(getContext().getPackageManager().getPackageInfo(requireContext().getPackageName(), 0).versionName); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } - mViewModel.getMeshNetworkLiveData().observe(this, meshNetworkLiveData -> { + mViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { if (meshNetworkLiveData != null) { networkNameView.setText(meshNetworkLiveData.getNetworkName()); - globalTtlView.setText(String.valueOf(meshNetworkLiveData.getGlobalTtl())); - final NetworkKey key = meshNetworkLiveData.getPrimaryNetworkKey(); - keyView.setText(MeshParserUtils.bytesToHex(key.getKey(), false)); - keyIndexView.setText(getString(R.string.hex_format, String.format(Locale.US, "%03X", key.getKeyIndex()))); - flagsView.setText(parseFlagsMessage(meshNetworkLiveData.getFlags())); - ivIndexView.setText(getString(R.string.hex_format, String.format(Locale.US, "%08X", meshNetworkLiveData.getIvIndex()))); - unicastAddressView.setText(getString(R.string.hex_format, String.format(Locale.US, "%04X", meshNetworkLiveData.getUnicastAddress()))); - manageAppKeysView.setText(getString(R.string.app_key_count, meshNetworkLiveData.getAppKeys().size())); - sourceAddressView.setText(MeshParserUtils.bytesToHex(meshNetworkLiveData.getProvisionerAddress(), true)); + netKeySummary.setText(String.valueOf(meshNetworkLiveData.getNetworkKeys().size())); + provisionerSummary.setText(String.valueOf(meshNetworkLiveData.getProvisioners().size())); + appKeySummary.setText(String.valueOf(meshNetworkLiveData.getAppKeys().size())); } }); @@ -260,17 +187,12 @@ public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final } @Override - public void onStart() { - super.onStart(); - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { inflater.inflate(R.menu.network_settings, menu); } @Override - public boolean onOptionsItemSelected(final MenuItem item) { + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { final int id = item.getItemId(); switch (id) { case R.id.action_import_network: @@ -291,97 +213,35 @@ public boolean onOptionsItemSelected(final MenuItem item) { return false; } + @SuppressWarnings("ConstantConditions") @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) { - case ManageAppKeysActivity.MANAGE_APP_KEYS: - if (resultCode == RESULT_OK) { - final int size = data.getExtras().getInt(RESULT_APP_KEY_LIST_SIZE); - manageAppKeysView.setText(getString(R.string.app_key_count, size)); + if (requestCode == READ_FILE_REQUEST_CODE) { + if (resultCode == RESULT_OK) { + if (data != null) { + final Uri uri = data.getData(); + mViewModel.getMeshManagerApi().importMeshNetwork(uri); } - break; - case READ_FILE_REQUEST_CODE: - if (resultCode == RESULT_OK) { - if (data != null) { - final Uri uri = data.getData(); - mViewModel.importMeshNetwork(uri); - } - } else { - Log.e(TAG, "Error while opening file browser"); - } - break; + } else { + Log.e(TAG, "Error while opening file browser"); + } } } @Override public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - switch (requestCode){ - case REQUEST_STORAGE_PERMISSION: - if(PackageManager.PERMISSION_GRANTED != grantResults[0]){ - Toast.makeText(getContext(), getString(R.string.ext_storage_permission_denied), Toast.LENGTH_LONG).show(); - } - break; + if (requestCode == REQUEST_STORAGE_PERMISSION) { + if (PackageManager.PERMISSION_GRANTED != grantResults[0]) { + Toast.makeText(getContext(), getString(R.string.ext_storage_permission_denied), Toast.LENGTH_LONG).show(); + } } } - private String parseFlagsMessage(final int flags) { - final int keyRefreshFlag = MeshParserUtils.getBitValue(flags, 0); - final int ivUpdateFlag = MeshParserUtils.getBitValue(flags, 1); - final StringBuilder flagsText = new StringBuilder(); - - if (keyRefreshFlag == 0) - flagsText.append(getString(R.string.key_refresh_phase_0)).append(", "); - else - flagsText.append(getString(R.string.key_refresh_phase_2)).append(", "); - - if (ivUpdateFlag == 0) - flagsText.append(getString(R.string.normal_operation)); - else - flagsText.append(getString(R.string.iv_update_active)); - - return flagsText.toString(); - } - - @Override - public void onNetworkNameEntered(final String networkName) { - mViewModel.getMeshNetworkLiveData().setNetworkName(networkName); - } - - @Override - public void onGlobalTtlEntered(final int globalTtl) { - mViewModel.getMeshNetworkLiveData().setGlobalTtl(globalTtl); - } - - @Override - public void onNetworkKeyGenerated(final String networkKey) { - mViewModel.getMeshNetworkLiveData().setPrimaryNetworkKey(networkKey); - } - - @Override - public void onKeyIndexGenerated(final int keyIndex) { - mViewModel.getMeshNetworkLiveData().setKeyIndex(keyIndex); - } - - @Override - public void onFlagsSelected(final int keyRefreshFlag, final int ivUpdateFlag) { - mViewModel.getMeshNetworkLiveData().setFlags(MeshParserUtils.parseUpdateFlags(keyRefreshFlag, ivUpdateFlag)); - } - - @Override - public void setIvIndex(final int ivIndex) { - mViewModel.getMeshNetworkLiveData().setIvIndex(ivIndex); - } - - @Override - public void setUnicastAddress(final int unicastAddress) { - mViewModel.getMeshNetworkLiveData().setUnicastAddress(unicastAddress); - } - @Override - public boolean setSourceAddress(final int sourceAddress) { - return mViewModel.getMeshNetworkLiveData().setProvisionerAddress(sourceAddress); + public void onNetworkNameEntered(@NonNull final String name) { + mViewModel.getNetworkLiveData().setNetworkName(name); } @Override @@ -403,7 +263,7 @@ public void requestPermission() { /** * Fires an intent to spin up the "file chooser" UI to select a file */ - public void performFileSearch() { + private void performFileSearch() { final Intent intent; if (Utils.isKitkatOrAbove()) { intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); @@ -426,7 +286,7 @@ private void handleNetworkExport() { fragmentPermissionRationale.show(getChildFragmentManager(), null); } else { final File f = new File(EXPORT_PATH); - if(!f.exists()) { + if (!f.exists()) { f.mkdirs(); } mViewModel.getMeshManagerApi().exportMeshNetwork(EXPORT_PATH); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/SplashScreenActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/SplashScreenActivity.java index a6e23ce08..157decf04 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/SplashScreenActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/SplashScreenActivity.java @@ -22,11 +22,11 @@ package no.nordicsemi.android.nrfmeshprovisioner; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import javax.inject.Inject; @@ -34,7 +34,7 @@ import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.SplashViewModel; public class SplashScreenActivity extends AppCompatActivity implements Injectable { - private static final int DURATION = 1000; + @Inject ViewModelProvider.Factory mViewModelFactory; @@ -43,7 +43,7 @@ protected void onCreate(final Bundle savedInstanceState) { setContentView(R.layout.activity_splash_screen); super.onCreate(savedInstanceState); final SplashViewModel viewModel = ViewModelProviders.of(SplashScreenActivity.this, mViewModelFactory).get(SplashViewModel.class); - viewModel.getMeshNetworkLiveData().observe(this,meshNetworkLiveData -> { + viewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { if(meshNetworkLiveData != null && meshNetworkLiveData.getMeshNetwork() != null) { navigateActivity(); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AddressTypeAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AddressTypeAdapter.java new file mode 100644 index 000000000..a97c40787 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AddressTypeAdapter.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes; + +public class AddressTypeAdapter extends BaseAdapter { + + private final AddressTypes[] mAddressTypes; + private final Context mContext; + + public AddressTypeAdapter(@NonNull final Context context, + @NonNull final AddressTypes[] addressTypes) { + this.mContext = context; + mAddressTypes = addressTypes; + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mAddressTypes.length; + } + + @Override + public AddressTypes getItem(final int position) { + return mAddressTypes[position]; + } + + @Override + public long getItemId(final int position) { + return position; + } + + @Override + public View getView(final int position, final View convertView, final ViewGroup parent) { + View view = convertView; + if (mAddressTypes.length > 0) { + ViewHolder viewHolder; + if (view == null) { + view = LayoutInflater.from(mContext).inflate(R.layout.address_type_item, parent, false); + viewHolder = new ViewHolder(view); + view.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) view.getTag(); + } + + final AddressTypes addressType = mAddressTypes[position]; + if (addressType == AddressTypes.GROUP_ADDRESS) { + viewHolder.addressName.setText(R.string.action_groups); + } else { + viewHolder.addressName.setText(AddressTypes.getTypeName(addressType)); + } + return view; + } else { + view = LayoutInflater.from(mContext).inflate(R.layout.no_groups_layout, parent, false); + return view; + } + } + + public final class ViewHolder { + + @BindView(R.id.address_name) + TextView addressName; + + private ViewHolder(final View view) { + ButterKnife.bind(this, view); + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AuthenticationOOBMethodsAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AuthenticationOOBMethodsAdapter.java index 689277948..eaaaf921b 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AuthenticationOOBMethodsAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AuthenticationOOBMethodsAdapter.java @@ -23,7 +23,7 @@ package no.nordicsemi.android.nrfmeshprovisioner.adapter; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -40,8 +40,8 @@ public class AuthenticationOOBMethodsAdapter extends BaseAdapter { - private final ArrayList mOOBTypes = new ArrayList<>(); private final Context mContext; + private final ArrayList mOOBTypes = new ArrayList<>(); /** * Constructs AuthenticationOOBMethodsAdapter diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ElementAdapterDetails.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ElementAdapterDetails.java deleted file mode 100644 index 0ee410e57..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ElementAdapterDetails.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2018, Nordic Semiconductor - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package no.nordicsemi.android.nrfmeshprovisioner.adapter; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.constraint.ConstraintLayout; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; - -import butterknife.BindView; -import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.models.VendorModel; -import no.nordicsemi.android.meshprovisioner.transport.Element; -import no.nordicsemi.android.meshprovisioner.transport.MeshModel; -import no.nordicsemi.android.meshprovisioner.utils.CompositionDataParser; -import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; -import no.nordicsemi.android.nrfmeshprovisioner.R; - -public class ElementAdapterDetails extends RecyclerView.Adapter { - - private final Context mContext; - private final List mElements; - private OnItemClickListener mOnItemClickListener; - - public ElementAdapterDetails(@NonNull final Context mContext, @NonNull final List elements) { - this.mContext = mContext; - mElements = elements; - } - - - public void setOnItemClickListener(final ElementAdapterDetails.OnItemClickListener listener) { - mOnItemClickListener = listener; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.element_item_details, parent, false); - return new ViewHolder(layoutView); - } - - @Override - public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) { - final Element element = mElements.get(position); - holder.mElementContainer.setTag(element); - final int modelCount = element.getSigModelCount() + element.getVendorModelCount(); - holder.mElementTitle.setText(mContext.getString(R.string.element_address, MeshAddress.formatAddress(element.getElementAddress(), false))); - holder.mElementSubtitle.setText(mContext.getString(R.string.model_count, modelCount)); - - final List models = new ArrayList<>(element.getMeshModels().values()); - inflateModelViews(holder, models); - - } - - private void inflateModelViews(final ElementAdapterDetails.ViewHolder holder, final List models){ - //Remove all child views to avoid duplicating - holder.mModelContainer.removeAllViews(); - for(MeshModel model : models) { - final View modelView = LayoutInflater.from(mContext).inflate(R.layout.model_item_details, holder.mElementContainer, false); - modelView.setTag(model.getModelId()); - final TextView modelNameView = modelView.findViewById(R.id.address); - final TextView modelIdView = modelView.findViewById(R.id.model_id); - modelNameView.setText(model.getModelName()); - if(model instanceof VendorModel){ - modelIdView.setText(mContext.getString(R.string.format_vendor_model_id, CompositionDataParser.formatModelIdentifier(model.getModelId(), true))); - } else { - modelIdView.setText(mContext.getString(R.string.format_sig_model_id, CompositionDataParser.formatModelIdentifier((short) model.getModelId(), true))); - } - - holder.mModelContainer.addView(modelView); - } - } - - @Override - public int getItemCount() { - return mElements.size(); - } - - public boolean isEmpty() { - return getItemCount() == 0; - } - - @FunctionalInterface - public interface OnItemClickListener { - void onItemClick(final int position); - } - - final class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ - @BindView(R.id.element_item_container) - ConstraintLayout mElementContainer; - @BindView(R.id.element_title) - TextView mElementTitle; - @BindView(R.id.element_subtitle) - TextView mElementSubtitle; - @BindView(R.id.element_expand) - ImageView mElementExpand; - @BindView(R.id.model_container) - LinearLayout mModelContainer; - - private ViewHolder(final View view) { - super(view); - ButterKnife.bind(this, view); - mElementContainer.setOnClickListener(this); - - } - - @Override - public void onClick(final View v) { - switch (v.getId()){ - case R.id.element_item_container: - if(mModelContainer.getVisibility() == View.VISIBLE){ - mElementExpand.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_round_expand_more_black_alpha_24dp)); - mModelContainer.setVisibility(View.GONE); - } else { - mElementExpand.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_round_expand_less_black_alpha_24dp)); - mModelContainer.setVisibility(View.VISIBLE); - } - mOnItemClickListener.onItemClick(getAdapterPosition()); - break; - default: - break; - } - } - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/FilterAddressAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/FilterAddressAdapter.java index d0c456027..1c7ff8563 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/FilterAddressAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/FilterAddressAdapter.java @@ -22,11 +22,8 @@ package no.nordicsemi.android.nrfmeshprovisioner.adapter; -import android.arch.lifecycle.LifecycleOwner; -import android.arch.lifecycle.LiveData; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -34,10 +31,12 @@ import java.util.ArrayList; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; import no.nordicsemi.android.meshprovisioner.utils.AddressArray; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.meshprovisioner.utils.ProxyFilter; import no.nordicsemi.android.nrfmeshprovisioner.R; @@ -45,23 +44,28 @@ public class FilterAddressAdapter extends RecyclerView.Adapter { - private final ArrayList mAddresses;// = new ArrayList<>(); + private final ArrayList mAddresses = new ArrayList<>(); private final Context mContext; private OnItemClickListener mOnItemClickListener; - public FilterAddressAdapter(@NonNull final Context context, @NonNull final LiveData meshNodeLiveData) { + public FilterAddressAdapter(@NonNull final Context context) { this.mContext = context; - mAddresses = new ArrayList<>(); - meshNodeLiveData.observe((LifecycleOwner) context, meshNode -> { - if (meshNode != null) { - final ProxyFilter proxyFilter = meshNode.getProxyFilter(); - if (proxyFilter != null) { - mAddresses.clear(); - mAddresses.addAll(proxyFilter.getAddresses()); - notifyDataSetChanged(); - } - } - }); + } + + public void updateData(@NonNull final ProxyFilter filter){ + mAddresses.clear(); + mAddresses.addAll(filter.getAddresses()); + notifyDataSetChanged(); + } + + public void clearData(){ + mAddresses.clear(); + notifyDataSetChanged(); + } + + public void clearRow(final int position){ + mAddresses.remove(position); + notifyDataSetChanged(); } public void setOnItemClickListener(final FilterAddressAdapter.OnItemClickListener listener) { @@ -76,17 +80,15 @@ public FilterAddressAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGro } @Override - public void onBindViewHolder(@NonNull final FilterAddressAdapter.ViewHolder holder, final int position) { - if (mAddresses.size() > 0) { - final byte[] address = mAddresses.get(position).getAddress(); - holder.address.setText(MeshParserUtils.bytesToHex(address, true)); - if (MeshParserUtils.isValidSubscriptionAddress(address)) { - holder.addressTitle.setText(R.string.title_group_address); - } else if (MeshParserUtils.isValidUnicastAddress(address)) { - holder.addressTitle.setText(R.string.title_unicast_address); - } else { - holder.addressTitle.setText(R.string.address); - } + public void onBindViewHolder(@NonNull final FilterAddressAdapter.ViewHolder holder, int position) { + final byte[] address = mAddresses.get(position).getAddress(); + holder.address.setText(MeshParserUtils.bytesToHex(address, true)); + if (MeshAddress.isValidGroupAddress(address)) { + holder.addressTitle.setText(R.string.title_group_address); + } else if (MeshAddress.isValidUnicastAddress(address)) { + holder.addressTitle.setText(R.string.title_unicast_address); + } else if (MeshAddress.isValidVirtualAddress(address)) { + holder.addressTitle.setText(R.string.virtual_address); } } @@ -113,7 +115,7 @@ public final class ViewHolder extends RemovableViewHolder { @BindView(R.id.address_id) TextView addressTitle; - @BindView(R.id.address) + @BindView(R.id.title) TextView address; private ViewHolder(final View view) { diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/FilterAddressAdapter1.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/FilterAddressAdapter1.java index d8fb06117..1b7b68ddb 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/FilterAddressAdapter1.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/FilterAddressAdapter1.java @@ -23,8 +23,8 @@ package no.nordicsemi.android.nrfmeshprovisioner.adapter; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -44,7 +44,7 @@ public class FilterAddressAdapter1 extends RecyclerView.Adapter mAddresses; private final Context mContext; - public FilterAddressAdapter1(@NonNull final Context context, final ArrayList addresses) { + public FilterAddressAdapter1(@NonNull final Context context, @NonNull final ArrayList addresses) { this.mContext = context; this.mAddresses = addresses; } @@ -85,7 +85,7 @@ public interface OnItemClickListener { final class ViewHolder extends RecyclerView.ViewHolder { - @BindView(R.id.address) + @BindView(R.id.title) TextView address; @BindView(R.id.img_delete) diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupAdapter.java index d15c38cef..8d35bce5e 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupAdapter.java @@ -23,8 +23,8 @@ package no.nordicsemi.android.nrfmeshprovisioner.adapter; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -49,19 +49,18 @@ public class GroupAdapter extends RecyclerView.Adapter private OnItemClickListener mOnItemClickListener; private MeshNetwork mNetwork; - public GroupAdapter(final Context context/*, final MeshNetwork meshNetwork, final List groupLiveData*/) { + public GroupAdapter(@NonNull final Context context) { this.mContext = context; } - public void updateAdapter(final MeshNetwork meshNetwork, final List groups){ - + public void updateAdapter(@NonNull final MeshNetwork meshNetwork, @NonNull final List groups) { mNetwork = meshNetwork; mGroups.clear(); mGroups.addAll(groups); notifyDataSetChanged(); } - public void setOnItemClickListener(final OnItemClickListener listener) { + public void setOnItemClickListener(@NonNull final OnItemClickListener listener) { mOnItemClickListener = listener; } @@ -79,9 +78,10 @@ public void onBindViewHolder(@NonNull final GroupAdapter.ViewHolder holder, fina if (group != null) { final List models = mNetwork.getModels(group); holder.groupName.setText(group.getName()); - final String addressSummary = "Address: " + MeshAddress.formatAddress(group.getGroupAddress(), true); - holder.groupAddress.setText(addressSummary); - holder.groupDeviceCount.setText(mContext.getString(R.string.group_device_count, models.size())); + holder.groupAddress.setText(mContext. + getString(R.string.group_address_summary, MeshAddress.formatAddress(group.getAddress(), true))); + holder.groupDeviceCount.setText(mContext.getResources(). + getQuantityString(R.plurals.device_count, models.size(), models.size())); } } } @@ -102,10 +102,11 @@ public boolean isEmpty() { /** * Returns the number of models associated to the group in a particular position + * * @param position position */ - public int getModelCount(final int position){ - if(position >= 0 && !mGroups.isEmpty() && position < mGroups.size()) { + public int getModelCount(final int position) { + if (position >= 0 && !mGroups.isEmpty() && position < mGroups.size()) { final Group group = mGroups.get(position); return mNetwork.getModels(group).size(); } @@ -130,7 +131,7 @@ private ViewHolder(final View view) { ButterKnife.bind(this, view); view.findViewById(R.id.container).setOnClickListener(v -> { if (mOnItemClickListener != null) { - mOnItemClickListener.onItemClick(mGroups.get(getAdapterPosition()).getGroupAddress()); + mOnItemClickListener.onItemClick(mGroups.get(getAdapterPosition()).getAddress()); } }); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupAdapterSpinner.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupAdapterSpinner.java index 184271419..75feeb1ad 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupAdapterSpinner.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupAdapterSpinner.java @@ -23,12 +23,11 @@ package no.nordicsemi.android.nrfmeshprovisioner.adapter; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; -import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; @@ -44,7 +43,6 @@ public class GroupAdapterSpinner extends BaseAdapter { private final ArrayList mGroups = new ArrayList<>(); private final Context mContext; - private OnItemClickListener mOnItemClickListener; public GroupAdapterSpinner(@NonNull final Context context, @NonNull final List groups) { this.mContext = context; @@ -53,10 +51,6 @@ public GroupAdapterSpinner(@NonNull final Context context, @NonNull final List mAddresses = new ArrayList<>(); - private OnItemClickListener mOnItemClickListener; - public GroupAddressAdapter(final BaseModelConfigurationActivity context, final MeshNetwork network, final LiveData meshModelLiveData) { + public GroupAddressAdapter(@NonNull final Context context, @NonNull final MeshNetwork network, @NonNull final LiveData meshModelLiveData) { this.mContext = context; this.network = network; - meshModelLiveData.observe(context, meshModel -> { - if(meshModel != null) { + meshModelLiveData.observe((LifecycleOwner) context, meshModel -> { + if (meshModel != null) { final List tempAddresses = meshModel.getSubscribedAddresses(); if (tempAddresses != null) { mAddresses.clear(); @@ -68,10 +67,6 @@ public GroupAddressAdapter(final BaseModelConfigurationActivity context, final M }); } - public void setOnItemClickListener(final GroupAddressAdapter.OnItemClickListener listener) { - mOnItemClickListener = listener; - } - @NonNull @Override public GroupAddressAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { @@ -81,16 +76,20 @@ public GroupAddressAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGrou @Override public void onBindViewHolder(@NonNull final GroupAddressAdapter.ViewHolder holder, final int position) { - if(mAddresses.size() > 0) { + if (mAddresses.size() > 0) { final int address = mAddresses.get(position); final Group group = network.getGroup(address); - if(group != null) { + if (group != null) { holder.icon.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_outline_group_work_black_alpha_24dp)); holder.name.setText(group.getName()); holder.address.setText(MeshAddress.formatAddress(address, true)); - } else { - holder.address.setText(MeshAddress.formatAddress(address, true)); - } + }/* else { + if(MeshAddress.isValidVirtualAddress(address)) { + holder.icon.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_label_outline_black_alpha_24dp)); + holder.name.setText(group.getName()); + holder.address.setText(model.getLabelUUID(address).toString().toUpperCase(Locale.US)); + } + }*/ } } @@ -108,28 +107,18 @@ public boolean isEmpty() { return getItemCount() == 0; } - @FunctionalInterface - public interface OnItemClickListener { - void onItemClick(final int position, final int address); - } - public final class ViewHolder extends RemovableViewHolder { @BindView(R.id.icon) ImageView icon; @BindView(R.id.address_id) TextView name; - @BindView(R.id.address) + @BindView(R.id.title) TextView address; private ViewHolder(final View view) { super(view); ButterKnife.bind(this, view); - view.findViewById(R.id.removable).setOnClickListener(v -> { - if (mOnItemClickListener != null) { - mOnItemClickListener.onItemClick(getAdapterPosition(), mAddresses.get(getAdapterPosition())); - } - }); } } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupModelAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupModelAdapter.java index 75a67510a..90e7c5809 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupModelAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/GroupModelAdapter.java @@ -23,10 +23,10 @@ package no.nordicsemi.android.nrfmeshprovisioner.adapter; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.constraint.ConstraintLayout; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -78,7 +78,7 @@ public void updateAdapter(@NonNull final Group group, @NonNull final ArrayList modelEntry : element.getMeshModels().entrySet()) { final MeshModel model = modelEntry.getValue(); for (Integer address : model.getSubscribedAddresses()) { - if (mGroup.getGroupAddress() == address) { + if (mGroup.getAddress() == address) { final View view = LayoutInflater.from(mContext).inflate(R.layout.group_model_item, holder.mModelContainer, false); final ConstraintLayout container = view.findViewById(R.id.container); final ImageView modelIcon = view.findViewById(R.id.icon); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ProvisioningProgressAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ProvisioningProgressAdapter.java index eeb2ff0d7..ffe5c5506 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ProvisioningProgressAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ProvisioningProgressAdapter.java @@ -23,9 +23,11 @@ package no.nordicsemi.android.nrfmeshprovisioner.adapter; import android.content.Context; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.RecyclerView; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -45,19 +47,20 @@ public class ProvisioningProgressAdapter extends RecyclerView.Adapter mProgress = new ArrayList<>(); - public ProvisioningProgressAdapter(final AppCompatActivity mContext, final ProvisioningStatusLiveData provisioningProgress) { + public ProvisioningProgressAdapter(@NonNull final Context mContext, @NonNull final ProvisioningStatusLiveData provisioningProgress) { this.mContext = mContext; this.mProgress.addAll(provisioningProgress.getStateList()); } + @NonNull @Override - public ProvisioningProgressAdapter.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { + public ProvisioningProgressAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.progress_item, parent, false); return new ProvisioningProgressAdapter.ViewHolder(layoutView); } @Override - public void onBindViewHolder(final ViewHolder holder, final int position) { + public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) { final ProvisionerProgress provisioningProgress = mProgress.get(position); if(provisioningProgress != null) { holder.image.setImageDrawable(ContextCompat.getDrawable(mContext, provisioningProgress.getResId())); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/SubGroupAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/SubGroupAdapter.java index 7631872d3..2ac9a14e0 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/SubGroupAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/SubGroupAdapter.java @@ -22,14 +22,7 @@ package no.nordicsemi.android.nrfmeshprovisioner.adapter; -import android.arch.lifecycle.LifecycleOwner; -import android.arch.lifecycle.LiveData; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.constraint.ConstraintLayout; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.CardView; -import android.support.v7.widget.RecyclerView; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.LayoutInflater; @@ -43,17 +36,25 @@ import java.util.List; +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; import no.nordicsemi.android.meshprovisioner.Group; import no.nordicsemi.android.meshprovisioner.MeshNetwork; import no.nordicsemi.android.meshprovisioner.models.SigModelParser; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; import no.nordicsemi.android.meshprovisioner.transport.MeshModel; import no.nordicsemi.android.meshprovisioner.utils.CompanyIdentifiers; import no.nordicsemi.android.meshprovisioner.utils.CompositionDataParser; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.MeshNetworkLiveData; public class SubGroupAdapter extends RecyclerView.Adapter { @@ -92,7 +93,7 @@ public void updateAdapterData() { } - public void setOnItemClickListener(final SubGroupAdapter.OnItemClickListener listener) { + public void setOnItemClickListener(@NonNull final SubGroupAdapter.OnItemClickListener listener) { mOnItemClickListener = listener; } @@ -109,8 +110,7 @@ public void onBindViewHolder(@NonNull final ViewHolder holder, final int positio final int keyIndex = mGroupedKeyModels.keyAt(position); holder.groupItemContainer.setTag(keyIndex); final ApplicationKey key = mMeshNetwork.getAppKey(keyIndex); - final String keyTitle = key.getName() + " " + keyIndex; - holder.mGroupAppKeyTitle.setText(keyTitle); + holder.mGroupAppKeyTitle.setText(key.getName()); final SparseIntArray groupedModels = mGroupedKeyModels.valueAt(position); holder.mGroupGrid.setRowCount(1); //Remove all child views to avoid duplicating @@ -134,37 +134,35 @@ private void inflateView(@NonNull final ViewHolder holder, final int keyIndex, f icon.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_domain_nordic_medium_gray_48dp)); view.findViewById(R.id.container_buttons).setVisibility(View.INVISIBLE); view.findViewById(R.id.container_vendor).setVisibility(View.VISIBLE); - final TextView modelIdView = view.findViewById(R.id.model_id); + final TextView modelIdView = view.findViewById(R.id.subtitle); modelIdView.setText(CompositionDataParser.formatModelIdentifier(modelId, true)); final TextView companyIdView = view.findViewById(R.id.company_id); final int companyIdentifier = MeshParserUtils.getCompanyIdentifier(modelId); companyIdView.setText(String.valueOf(CompanyIdentifiers.getCompanyName((short) companyIdentifier))); - groupSummary.setText(mContext.getString(R.string.unknown_device_count, modelCount)); + groupSummary.setText(mContext.getResources().getQuantityString(R.plurals.device_count, modelCount, modelCount)); } else { switch (modelId) { case SigModelParser.GENERIC_ON_OFF_SERVER: - groupSummary.setText(mContext.getString(R.string.light_count, modelCount)); + groupSummary.setText(mContext.getResources().getQuantityString(R.plurals.light_count, modelCount, modelCount)); break; case SigModelParser.GENERIC_ON_OFF_CLIENT: icon.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_light_switch_nordic_medium_grey_48dp)); view.findViewById(R.id.container_buttons).setVisibility(View.INVISIBLE); - groupSummary.setText(mContext.getString(R.string.switch_count, modelCount)); + groupSummary.setText(mContext.getResources().getQuantityString(R.plurals.switch_count, modelCount, modelCount)); break; case SigModelParser.GENERIC_LEVEL_SERVER: icon.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_lightbulb_level_nordic_sun_outline_48dp)); - groupSummary.setText(mContext.getString(R.string.dimmer_count, modelCount)); + groupSummary.setText(mContext.getResources().getQuantityString(R.plurals.dimmer_count, modelCount, modelCount)); break; default: icon.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_help_outline_nordic_medium_grey_48dp)); view.findViewById(R.id.container_buttons).setVisibility(View.INVISIBLE); - groupSummary.setText(mContext.getString(R.string.unknown_device_count, modelCount)); + groupSummary.setText(mContext.getResources().getQuantityString(R.plurals.device_count, modelCount, modelCount)); break; } } - groupContainerCard.setOnClickListener(v -> { - onSubGroupItemClicked(keyIndex, modelId); - }); + groupContainerCard.setOnClickListener(v -> onSubGroupItemClicked(keyIndex, modelId)); on.setOnClickListener(v -> toggleState(keyIndex, modelId, true)); off.setOnClickListener(v -> toggleState(keyIndex, modelId, false)); @@ -201,11 +199,11 @@ public long getItemId(final int position) { return super.getItemId(position); } - public int getModelCount(){ + public int getModelCount() { return mModels.size(); } - public List getModels(){ + public List getModels() { return mModels; } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/BleMeshManager.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/BleMeshManager.java index 06bfab2b4..7e199d85b 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/BleMeshManager.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/BleMeshManager.java @@ -27,7 +27,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import java.lang.reflect.Method; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ReconnectActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/ReconnectActivity.java similarity index 81% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ReconnectActivity.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/ReconnectActivity.java index 8badba10f..7f40e43ca 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ReconnectActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/ReconnectActivity.java @@ -20,27 +20,25 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.ble; import android.app.Activity; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import javax.inject.Inject; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigSetFilterType; -import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; -import no.nordicsemi.android.meshprovisioner.utils.ProxyFilterType; +import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; @@ -69,6 +67,7 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { final Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); + //noinspection ConstantConditions getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(deviceName); getSupportActionBar().setSubtitle(deviceAddress); @@ -91,9 +90,6 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { returnIntent.putExtra(Utils.EXTRA_DATA, true); setResult(Activity.RESULT_OK, returnIntent); finish(); - //We send a proxy whitelist filter message to identify the node we are connected to when reconnecting to the network - final ProxyConfigSetFilterType setFilterType = new ProxyConfigSetFilterType(new ProxyFilterType(ProxyFilterType.WHITE_LIST_FILTER)); - mReconnectViewModel.getMeshManagerApi().sendMeshMessage(MeshAddress.UNASSIGNED_ADDRESS, setFilterType); } }); @@ -101,10 +97,9 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { @Override public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; } return false; } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ScannerActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/ScannerActivity.java similarity index 86% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ScannerActivity.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/ScannerActivity.java index 98f6bdb11..df61d61ff 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ScannerActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/ScannerActivity.java @@ -20,44 +20,46 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.ble; import android.Manifest; import android.app.Activity; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; import android.bluetooth.BluetoothAdapter; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.SimpleItemAnimator; -import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; import android.widget.Button; import javax.inject.Inject; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.DevicesAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.ProvisioningActivity; +import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; -import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; +import no.nordicsemi.android.nrfmeshprovisioner.ble.adapter.DevicesAdapter; import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ScannerLiveData; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ScannerViewModel; -public class ScannerActivity extends AppCompatActivity implements Injectable, DevicesAdapter.OnItemClickListener { +public class ScannerActivity extends AppCompatActivity implements Injectable, + DevicesAdapter.OnItemClickListener { private static final int REQUEST_ACCESS_COARSE_LOCATION = 1022; // random number @Inject @@ -89,17 +91,16 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { // Create view model containing utility methods for scanning mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(ScannerViewModel.class); - mViewModel.getScannerRepository().getScannerState().startScanning(); - mViewModel.getScannerRepository().getScannerState().observe(this, this::startScan); final Toolbar toolbar = findViewById(R.id.toolbar); + toolbar.setTitle(R.string.title_scanner); setSupportActionBar(toolbar); + //noinspection ConstantConditions getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.title_scanner); if (getIntent() != null) { mScanWithProxyService = getIntent().getBooleanExtra(Utils.EXTRA_DATA_PROVISIONING_SERVICE, true); - if(mScanWithProxyService) { + if (mScanWithProxyService) { getSupportActionBar().setSubtitle(R.string.sub_title_scanning_nodes); } else { getSupportActionBar().setSubtitle(R.string.sub_title_scanning_proxy_node); @@ -111,7 +112,10 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { recyclerViewDevices.setLayoutManager(new LinearLayoutManager(this)); final DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerViewDevices.getContext(), DividerItemDecoration.VERTICAL); recyclerViewDevices.addItemDecoration(dividerItemDecoration); - ((SimpleItemAnimator) recyclerViewDevices.getItemAnimator()).setSupportsChangeAnimations(false); + + final SimpleItemAnimator itemAnimator = (SimpleItemAnimator) recyclerViewDevices.getItemAnimator(); + if (itemAnimator != null) itemAnimator.setSupportsChangeAnimations(false); + final DevicesAdapter adapter = new DevicesAdapter(this, mViewModel.getScannerRepository().getScannerState()); adapter.setOnItemClickListener(this); recyclerViewDevices.setAdapter(adapter); @@ -119,6 +123,13 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { } + @Override + protected void onStart() { + super.onStart(); + mViewModel.getScannerRepository().getScannerState().startScanning(); + mViewModel.getScannerRepository().getScannerState().observe(this, this::startScan); + } + @Override protected void onStop() { super.onStop(); @@ -127,10 +138,9 @@ protected void onStop() { @Override public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; } return false; } @@ -163,7 +173,7 @@ public void onItemClick(final ExtendedBluetoothDevice device) { final Intent intent; stopScan(); if (mScanWithProxyService) { - intent = new Intent(this, MeshProvisionerActivity.class); + intent = new Intent(this, ProvisioningActivity.class); intent.putExtra(Utils.EXTRA_DEVICE, device); startActivityForResult(intent, Utils.PROVISIONING_SUCCESS); } else { @@ -176,10 +186,8 @@ public void onItemClick(final ExtendedBluetoothDevice device) { @Override public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - switch (requestCode) { - case REQUEST_ACCESS_COARSE_LOCATION: - mViewModel.getScannerRepository().getScannerState().refresh(); - break; + if (requestCode == REQUEST_ACCESS_COARSE_LOCATION) { + mViewModel.getScannerRepository().getScannerState().refresh(); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/DevicesAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/adapter/DevicesAdapter.java similarity index 92% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/DevicesAdapter.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/adapter/DevicesAdapter.java index a1bafa319..edb5037a5 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/DevicesAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ble/adapter/DevicesAdapter.java @@ -20,12 +20,12 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.adapter; +package no.nordicsemi.android.nrfmeshprovisioner.ble.adapter; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v4.app.FragmentActivity; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; +import androidx.recyclerview.widget.RecyclerView; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -38,6 +38,7 @@ import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ScannerLiveData; public class DevicesAdapter extends RecyclerView.Adapter { @@ -57,7 +58,7 @@ public DevicesAdapter(final FragmentActivity fragmentActivity, final ScannerLive }); } - public void setOnItemClickListener(final OnItemClickListener listener) { + public void setOnItemClickListener(@NonNull final OnItemClickListener listener) { mOnItemClickListener = listener; } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ActivitiesModule.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ActivitiesModule.java index 1d3c18a77..7a647c2d9 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ActivitiesModule.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ActivitiesModule.java @@ -24,71 +24,115 @@ import dagger.Module; import dagger.android.ContributesAndroidInjector; -import no.nordicsemi.android.nrfmeshprovisioner.BaseModelConfigurationActivity; -import no.nordicsemi.android.nrfmeshprovisioner.ConfigurationServerActivity; -import no.nordicsemi.android.nrfmeshprovisioner.GenericLevelServerActivity; -import no.nordicsemi.android.nrfmeshprovisioner.GenericOnOffServerActivity; import no.nordicsemi.android.nrfmeshprovisioner.GroupControlsActivity; import no.nordicsemi.android.nrfmeshprovisioner.MainActivity; -import no.nordicsemi.android.nrfmeshprovisioner.ManageAppKeysActivity; -import no.nordicsemi.android.nrfmeshprovisioner.MeshProvisionerActivity; -import no.nordicsemi.android.nrfmeshprovisioner.ModelConfigurationActivity; -import no.nordicsemi.android.nrfmeshprovisioner.NodeConfigurationActivity; -import no.nordicsemi.android.nrfmeshprovisioner.NodeDetailsActivity; -import no.nordicsemi.android.nrfmeshprovisioner.PublicationSettingsActivity; -import no.nordicsemi.android.nrfmeshprovisioner.ReconnectActivity; -import no.nordicsemi.android.nrfmeshprovisioner.ScannerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.ProvisioningActivity; import no.nordicsemi.android.nrfmeshprovisioner.SplashScreenActivity; -import no.nordicsemi.android.nrfmeshprovisioner.VendorModelActivity; +import no.nordicsemi.android.nrfmeshprovisioner.ble.ReconnectActivity; +import no.nordicsemi.android.nrfmeshprovisioner.ble.ScannerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AddAppKeyActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AddAppKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AddNetKeyActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AddNetKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.EditAppKeyActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.EditNetKeyActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.NetKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.ConfigurationClientActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.ConfigurationServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.GenericLevelServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.GenericOnOffServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.ModelConfigurationActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.NodeConfigurationActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.NodeDetailsActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.PublicationSettingsActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.VendorModelActivity; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.AddProvisionerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.EditProvisionerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.ProvisionersActivity; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.RangesActivity; @Module abstract class ActivitiesModule { - @ContributesAndroidInjector() - abstract SplashScreenActivity contributeSplashScreenActivity(); + @ContributesAndroidInjector() + abstract SplashScreenActivity contributeSplashScreenActivity(); - @ContributesAndroidInjector(modules = FragmentBuildersModule.class) - abstract MainActivity contributeMainActivity(); + @ContributesAndroidInjector(modules = FragmentBuildersModule.class) + abstract MainActivity contributeMainActivity(); - @ContributesAndroidInjector() - abstract ManageAppKeysActivity contributeManageAppKeysActivity(); + @ContributesAndroidInjector() + abstract ProvisionersActivity contributeProvisionersActivity(); - @ContributesAndroidInjector() - abstract MeshProvisionerActivity contributeMeshProvisionerActivity(); + @ContributesAndroidInjector() + abstract AddProvisionerActivity contributeAddProvisionersActivity(); - @ContributesAndroidInjector() - abstract NodeConfigurationActivity contributeElementConfigurationActivity(); + @ContributesAndroidInjector() + abstract EditProvisionerActivity contributeEditProvisionersActivity(); - @ContributesAndroidInjector() - abstract BaseModelConfigurationActivity contributeBaseModelConfigurationActivity(); + @ContributesAndroidInjector() + abstract RangesActivity contributeRangesActivity(); - @ContributesAndroidInjector() - abstract ScannerActivity contributeScannerActivity(); + @ContributesAndroidInjector() + abstract NetKeysActivity contributeNetKeysActivity(); - @ContributesAndroidInjector() - abstract ReconnectActivity contributeReconnectActivity(); + @ContributesAndroidInjector() + abstract AddNetKeyActivity contributeAddNetKeyActivity(); - @ContributesAndroidInjector() - abstract NodeDetailsActivity contributeNodeDetailsActivity(); + @ContributesAndroidInjector() + abstract EditNetKeyActivity contributeEditNetKeyActivity(); - @ContributesAndroidInjector() - abstract GroupControlsActivity contributeGroupControlsActivity(); + @ContributesAndroidInjector() + abstract AppKeysActivity contributeAppKeysActivity(); - @ContributesAndroidInjector() - abstract PublicationSettingsActivity contributePublicationSettingsActivity(); + @ContributesAndroidInjector() + abstract AddAppKeyActivity contributeAddAppKeyActivity(); - @ContributesAndroidInjector() - abstract ConfigurationServerActivity contribyteConfigurationServerActivity(); + @ContributesAndroidInjector() + abstract EditAppKeyActivity contributeEditAppKeyActivity(); - @ContributesAndroidInjector() - abstract GenericOnOffServerActivity contributeGenericOnOffServerActivity(); + @ContributesAndroidInjector() + abstract ProvisioningActivity contributeMeshProvisionerActivity(); - @ContributesAndroidInjector() - abstract GenericLevelServerActivity contributeGenericLevelServerActivity(); + @ContributesAndroidInjector() + abstract NodeConfigurationActivity contributeElementConfigurationActivity(); - @ContributesAndroidInjector() - abstract VendorModelActivity contributeVendorModelActivity(); + @ContributesAndroidInjector() + abstract AddAppKeysActivity contributeAddAppKeysActivity(); - @ContributesAndroidInjector() - abstract ModelConfigurationActivity contributeModelConfigurationActivity(); + @ContributesAndroidInjector() + abstract AddNetKeysActivity contributeAddNetKeysActivity(); + + @ContributesAndroidInjector() + abstract ScannerActivity contributeScannerActivity(); + + @ContributesAndroidInjector() + abstract ReconnectActivity contributeReconnectActivity(); + + @ContributesAndroidInjector() + abstract NodeDetailsActivity contributeNodeDetailsActivity(); + + @ContributesAndroidInjector() + abstract GroupControlsActivity contributeGroupControlsActivity(); + + @ContributesAndroidInjector() + abstract PublicationSettingsActivity contributePublicationSettingsActivity(); + + @ContributesAndroidInjector() + abstract ConfigurationServerActivity contributeConfigurationServerActivity(); + + @ContributesAndroidInjector() + abstract ConfigurationClientActivity contributeConfigurationClientActivity(); + + @ContributesAndroidInjector() + abstract GenericOnOffServerActivity contributeGenericOnOffServerActivity(); + + @ContributesAndroidInjector() + abstract GenericLevelServerActivity contributeGenericLevelServerActivity(); + + @ContributesAndroidInjector() + abstract VendorModelActivity contributeVendorModelActivity(); + + @ContributesAndroidInjector() + abstract ModelConfigurationActivity contributeModelConfigurationActivity(); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/AppInjector.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/AppInjector.java index cf2c574de..a281d7f82 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/AppInjector.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/AppInjector.java @@ -25,9 +25,11 @@ import android.app.Activity; import android.app.Application; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v7.app.AppCompatActivity; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.appcompat.app.AppCompatActivity; import dagger.android.AndroidInjection; import dagger.android.support.AndroidSupportInjection; @@ -36,11 +38,11 @@ /** * Helper class to automatically inject fragments if they implement {@link Injectable}. */ -public class AppInjector { +class AppInjector { private AppInjector() { } - public static void init(final MeshApplication application) { + static void init(final MeshApplication application) { DaggerMeshAppComponent.builder() .contextModule(new ContextModule(application)) .build() @@ -92,7 +94,7 @@ private static void handleActivity(final Activity activity) { ((AppCompatActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks( new FragmentManager.FragmentLifecycleCallbacks() { @Override - public void onFragmentCreated(final FragmentManager fm, final Fragment f, final Bundle savedInstanceState) { + public void onFragmentCreated(@NonNull final FragmentManager fm, @NonNull final Fragment f, final Bundle savedInstanceState) { if (f instanceof Injectable) { AndroidSupportInjection.inject(f); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/BleMeshManagerModule.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/BleMeshManagerModule.java index f97e88e8c..22fc41f9b 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/BleMeshManagerModule.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/BleMeshManagerModule.java @@ -30,7 +30,6 @@ import dagger.Provides; import no.nordicsemi.android.meshprovisioner.MeshManagerApi; import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NetworkInformation; @Module class BleMeshManagerModule { @@ -45,9 +44,4 @@ BleMeshManager provideBleMeshManager(final Context context) { MeshManagerApi provideMeshManagerApi(final Context context) { return new MeshManagerApi(context); } - - @Provides - NetworkInformation provideNetworkInformation(final Context context) { - return new NetworkInformation(context); - } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/FragmentBuildersModule.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/FragmentBuildersModule.java index bf92ad9d9..49aa477d1 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/FragmentBuildersModule.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/FragmentBuildersModule.java @@ -26,6 +26,7 @@ import dagger.android.ContributesAndroidInjector; import no.nordicsemi.android.nrfmeshprovisioner.GroupsFragment; import no.nordicsemi.android.nrfmeshprovisioner.NetworkFragment; +import no.nordicsemi.android.nrfmeshprovisioner.ProxyFilterFragment; import no.nordicsemi.android.nrfmeshprovisioner.SettingsFragment; @Module @@ -35,5 +36,7 @@ abstract class FragmentBuildersModule { @ContributesAndroidInjector abstract GroupsFragment contributeGroupFragment(); @ContributesAndroidInjector + abstract ProxyFilterFragment contributeProxyFilterFragment(); + @ContributesAndroidInjector abstract SettingsFragment contributeSettingsFragment(); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/MeshApplication.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/MeshApplication.java index 32e567a37..19f49b9b6 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/MeshApplication.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/MeshApplication.java @@ -24,8 +24,8 @@ import android.app.Activity; import android.app.Service; -import android.support.multidex.MultiDexApplication; -import android.support.v7.app.AppCompatDelegate; +import androidx.multidex.MultiDexApplication; +import androidx.appcompat.app.AppCompatDelegate; import javax.inject.Inject; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ViewModelModule.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ViewModelModule.java index 1ab47d187..45de85d7a 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ViewModelModule.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ViewModelModule.java @@ -22,40 +22,38 @@ package no.nordicsemi.android.nrfmeshprovisioner.di; -import android.arch.lifecycle.ViewModelProvider; import android.content.Context; import javax.inject.Singleton; +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModelProvider; import dagger.Module; import dagger.Provides; import no.nordicsemi.android.meshprovisioner.MeshManagerApi; import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NetworkInformation; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NrfMeshRepository; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ScannerRepository; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ViewModelFactory; - @Module(subcomponents = ViewModelSubComponent.class) class ViewModelModule { - @Provides - static ScannerRepository provideScannerRepository(final Context context, final MeshManagerApi meshManagerApi) { - return new ScannerRepository(context, meshManagerApi); - } - - @Provides - @Singleton - static NrfMeshRepository provideNrfMeshRepository(final MeshManagerApi meshManagerApi, - final NetworkInformation networkInformation, - final BleMeshManager bleMeshManager) { - return new NrfMeshRepository(meshManagerApi, networkInformation, bleMeshManager); - } - - @Provides - @Singleton - static ViewModelProvider.Factory provideViewModelFactory(final ViewModelSubComponent.Builder viewModelSubComponent) { - return new ViewModelFactory(viewModelSubComponent.build()); - } + @Provides + static ScannerRepository provideScannerRepository(@NonNull final Context context, @NonNull final MeshManagerApi meshManagerApi) { + return new ScannerRepository(context, meshManagerApi); + } + + @Provides + @Singleton + static NrfMeshRepository provideNrfMeshRepository(@NonNull final MeshManagerApi meshManagerApi, + @NonNull final BleMeshManager bleMeshManager) { + return new NrfMeshRepository(meshManagerApi, bleMeshManager); + } + + @Provides + @Singleton + static ViewModelProvider.Factory provideViewModelFactory(@NonNull final ViewModelSubComponent.Builder viewModelSubComponent) { + return new ViewModelFactory(viewModelSubComponent.build()); + } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ViewModelSubComponent.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ViewModelSubComponent.java index 6ff547c42..c9df8be40 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ViewModelSubComponent.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/di/ViewModelSubComponent.java @@ -23,12 +23,23 @@ package no.nordicsemi.android.nrfmeshprovisioner.di; import dagger.Subcomponent; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AddAppKeyViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AddKeysViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AddNetKeyViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AddProvisionerViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AppKeysViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.EditAppKeyViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.EditNetKeyViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.EditProvisionerViewModel; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.GroupControlsViewModel; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ManageAppKeysViewModel; -import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.MeshProvisionerViewModel; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ModelConfigurationViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NetKeysViewModel; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NodeConfigurationViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NodeDetailsViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ProvisionersViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ProvisioningViewModel; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.PublicationViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.RangesViewModel; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ReconnectViewModel; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ScannerViewModel; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.SharedViewModel; @@ -42,18 +53,50 @@ */ @Subcomponent public interface ViewModelSubComponent { - @Subcomponent.Builder - interface Builder { - ViewModelSubComponent build(); - } - SplashViewModel splashViewModel(); - SharedViewModel commonViewModel(); - ScannerViewModel scannerViewModel(); - GroupControlsViewModel groupControlsViewModel(); - ManageAppKeysViewModel manageAppKeysViewModel(); - MeshProvisionerViewModel meshProvisionerViewModel(); - NodeConfigurationViewModel meshConfigurationViewModel(); - ModelConfigurationViewModel modelConfigurationViewModel(); - PublicationViewModel publicationViewModel(); - ReconnectViewModel reconnectViewModule(); + @Subcomponent.Builder + interface Builder { + ViewModelSubComponent build(); + } + + SplashViewModel splashViewModel(); + + SharedViewModel commonViewModel(); + + ScannerViewModel scannerViewModel(); + + GroupControlsViewModel groupControlsViewModel(); + + ProvisionersViewModel provisionersViewModel(); + + AddProvisionerViewModel addProvisionerViewModel(); + + EditProvisionerViewModel editProvisionerViewModel(); + + RangesViewModel rangesViewModel(); + + NetKeysViewModel netKeysViewModel(); + + AddNetKeyViewModel addNetKeyViewModel(); + + EditNetKeyViewModel editNetKeyViewModel(); + + AppKeysViewModel appKeysViewModel(); + + AddAppKeyViewModel addAppKeyViewModel(); + + EditAppKeyViewModel editAppKeyViewModel(); + + ProvisioningViewModel meshProvisionerViewModel(); + + NodeConfigurationViewModel nodeConfigurationViewModel(); + + AddKeysViewModel addKeysViewModel(); + + NodeDetailsViewModel nodeDetailsViewModel(); + + ModelConfigurationViewModel modelConfigurationViewModel(); + + PublicationViewModel publicationViewModel(); + + ReconnectViewModel reconnectViewModule(); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmenPermissionDenied.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmenPermissionDenied.java index 073d956a7..8127a53de 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmenPermissionDenied.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmenPermissionDenied.java @@ -24,8 +24,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAuthenticationInput.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAuthenticationInput.java index 408a4f447..935803c26 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAuthenticationInput.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAuthenticationInput.java @@ -23,14 +23,15 @@ package no.nordicsemi.android.nrfmeshprovisioner.dialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; @@ -99,11 +100,12 @@ public void onCreate(final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.dialog_fragment_auth_input, null); ButterKnife.bind(this, rootView); AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()). - setIcon(R.drawable.ic_lock_open). + setIcon(R.drawable.ic_lock_open_black_alpha_24dp). setTitle(getString(R.string.provisioner_authentication_title)). setView(rootView); @@ -245,7 +247,7 @@ private void updateInputOOBUI() { //noinspection ConstantConditions final int authInput = MeshParserUtils.unsignedByteToInt(authValue[0]); if (inputOOBAction == InputOOBAction.PUSH) { - msg = getString(R.string.provisioner_input_pushes, authInput); + msg = getResources().getQuantityString(R.plurals.input_pushes, authInput, authInput); } else { msg = getString(authInput); } @@ -253,14 +255,12 @@ private void updateInputOOBUI() { start = msg.indexOf(String.valueOf(authInput)); end = start + String.valueOf(authInput).length(); } else if (inputOOBAction == InputOOBAction.INPUT_NUMERIC) { - //noinspection ConstantConditions final String authString = String.valueOf(ByteBuffer.wrap(authValue).getInt()); msg = getString(R.string.provisioner_input_numeric_device, authString); start = msg.indexOf(authString); end = start + authString.length(); spannableMessage = new SpannableStringBuilder(msg); } else { - //noinspection ConstantConditions final String authString = new String(authValue); msg = getString(R.string.provisioner_input_numeric_device, authString); start = msg.indexOf(authString); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigStatus.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigStatus.java index c69cd1c46..d1e7f2e24 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigStatus.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigStatus.java @@ -24,17 +24,13 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; public class DialogFragmentConfigStatus extends DialogFragmentMessage { - public interface DialogFragmentAppKeyBindStatusListener { - void onAppKeyBindStatusConfirmed(); - } - public static DialogFragmentConfigStatus newInstance(final String title, final String message) { Bundle args = new Bundle(); DialogFragmentConfigStatus fragment = new DialogFragmentConfigStatus(); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAppKeyAddStatus.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigurationComplete.java similarity index 76% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAppKeyAddStatus.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigurationComplete.java index a86ae37ad..5bce6ba5a 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAppKeyAddStatus.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigurationComplete.java @@ -24,20 +24,20 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; -public class DialogFragmentAppKeyAddStatus extends DialogFragmentMessage { +public class DialogFragmentConfigurationComplete extends DialogFragmentMessage { - public interface DialogFragmentAppKeyAddStatusListener { - void onAppKeyAddStatusReceived(); + public interface ConfigurationCompleteListener { + void onConfigurationCompleted(); } - public static DialogFragmentAppKeyAddStatus newInstance(final String title, final String message) { + public static DialogFragmentConfigurationComplete newInstance(final String title, final String message) { Bundle args = new Bundle(); - DialogFragmentAppKeyAddStatus fragment = new DialogFragmentAppKeyAddStatus(); + DialogFragmentConfigurationComplete fragment = new DialogFragmentConfigurationComplete(); args.putString(TITLE, title); args.putString(MESSAGE, message); fragment.setArguments(args); @@ -52,10 +52,10 @@ public void onCreate(Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - alertDialogBuilder = new AlertDialog.Builder(getActivity()); - alertDialogBuilder.setIcon(R.drawable.ic_vpn_key_black_alpha_24dp); + alertDialogBuilder = new AlertDialog.Builder(requireContext()); + alertDialogBuilder.setIcon(R.drawable.ic_done_all_black_alpha_24dp); alertDialogBuilder.setPositiveButton(getString(R.string.ok), (dialog, which) -> ( - (DialogFragmentAppKeyAddStatusListener)getActivity()).onAppKeyAddStatusReceived()); + (ConfigurationCompleteListener)requireActivity()).onConfigurationCompleted()); return super.onCreateDialog(savedInstanceState); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentCreateGroup.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentCreateGroup.java index 314f4761c..0c8fe6988 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentCreateGroup.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentCreateGroup.java @@ -22,34 +22,61 @@ package no.nordicsemi.android.nrfmeshprovisioner.dialog; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.KeyListener; import android.view.LayoutInflater; import android.view.View; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.Spinner; +import android.widget.TextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import java.util.Locale; +import java.util.UUID; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.Group; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.GroupCallbacks; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.adapter.AddressTypeAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes; import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.GROUP_ADDRESS; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.VIRTUAL_ADDRESS; + public class DialogFragmentCreateGroup extends DialogFragment { - private static final String GROUPS = "GROUPS"; + private static final AddressTypes[] addressTypes = {GROUP_ADDRESS, VIRTUAL_ADDRESS}; + private static final String GROUP = "GROUP"; + //UI Bindings + @BindView(R.id.summary) + TextView summary; + @BindView(R.id.address_types) + Spinner addressTypesSpinnerView; + @BindView(R.id.group_container) + View groupContainer; @BindView(R.id.group_name_layout) TextInputLayout groupNameInputLayout; @BindView(R.id.name_input) @@ -58,11 +85,15 @@ public class DialogFragmentCreateGroup extends DialogFragment { TextInputLayout addressInputLayout; @BindView(R.id.address_input) TextInputEditText addressInput; + @BindView(R.id.label_summary) + TextView labelSummary; + @BindView(R.id.uuid_label) + TextView labelUuidView; + private Button mGenerateLabelUUID; - public interface DialogFragmentCreateGroupListener { - boolean createGroup(@NonNull final String name, final int address); - } + private AddressTypeAdapter mAdapterSpinner; + private Group mGroup; public static DialogFragmentCreateGroup newInstance() { return new DialogFragmentCreateGroup(); @@ -76,12 +107,27 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_create_group, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_group_subscription, null); //Bind ui ButterKnife.bind(this, rootView); - final KeyListener hexKeyListener = new HexKeyListener(); + if (savedInstanceState == null) { + mGroup = ((GroupCallbacks) requireParentFragment()).createGroup(); + } else { + mGroup = savedInstanceState.getParcelable(GROUP); + } + + if (mGroup != null) { + groupNameInput.setText(mGroup.getName()); + addressInput.setText(MeshAddress.formatAddress(mGroup.getAddress(), false)); + } + updateGroup(); + + mAdapterSpinner = new AddressTypeAdapter(requireContext(), addressTypes); + addressTypesSpinnerView.setAdapter(mAdapterSpinner); + + groupContainer.setVisibility(View.GONE); addressInput.setKeyListener(hexKeyListener); addressInput.addTextChangedListener(new TextWatcher() { @Override @@ -91,6 +137,7 @@ public void beforeTextChanged(final CharSequence s, final int start, final int c @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + mGroup = null; if (TextUtils.isEmpty(s.toString())) { addressInputLayout.setError(getString(R.string.error_empty_group_address)); } else { @@ -104,33 +151,109 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); + addressTypesSpinnerView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(final AdapterView parent, final View view, final int position, final long id) { + updateAddress(mAdapterSpinner.getItem(position)); + } + + @Override + public void onNothingSelected(final AdapterView parent) { + + } + }); + + final AlertDialog alertDialog = new AlertDialog.Builder(requireContext()) + .setView(rootView) + .setIcon(R.drawable.ic_outline_group_work_black_alpha_24dp) + .setTitle(R.string.title_create_group) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .setNeutralButton(R.string.generate_uuid, null).show(); - alertDialogBuilder.setIcon(R.drawable.ic_outline_group_work_black_alpha_24dp); - alertDialogBuilder.setTitle(R.string.title_create_group); + summary.setText(R.string.title_create_group_summary); - final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String name = groupNameInput.getEditableText().toString(); - final String address = addressInput.getEditableText().toString(); - if (validateInput(name, address)) { - if(getParentFragment() != null) { - if(((DialogFragmentCreateGroupListener) getParentFragment()).createGroup(name, Integer.valueOf(address, 16))) { + final String name = groupNameInput.getEditableText().toString().trim(); + try { + final AddressTypes type = (AddressTypes) addressTypesSpinnerView.getSelectedItem(); + if (type == GROUP_ADDRESS) { + if (mGroup == null) { + final String address = addressInput.getEditableText().toString().trim(); + if (validateInput(name, address)) { + if (((GroupCallbacks) requireParentFragment()).onGroupAdded(name, Integer.valueOf(address, 16))) { + dismiss(); + } else { + addressInputLayout.setError(getString(R.string.error_group_address_in_used)); + } + } + } else { + if (((GroupCallbacks) requireParentFragment()).onGroupAdded(mGroup)) { + dismiss(); + } + } + } else { + + final UUID uuid = UUID.fromString(labelUuidView.getText().toString()); + final Group group = ((GroupCallbacks) requireParentFragment()).createGroup(uuid, name); + if (group != null) { + if (((GroupCallbacks) requireParentFragment()).onGroupAdded(group)) { dismiss(); - } else { - addressInputLayout.setError(getString(R.string.error_group_address_in_used)); } } } + } catch (IllegalArgumentException ex) { + addressInputLayout.setError(ex.getMessage()); + } }); + mGenerateLabelUUID = alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL); + mGenerateLabelUUID.setOnClickListener(v -> { + final UUID uuid = MeshAddress.generateRandomLabelUUID(); + labelUuidView.setText(uuid.toString().toUpperCase(Locale.US)); + final Integer add = MeshAddress.generateVirtualAddress(uuid); + addressInput.setText(MeshAddress.formatAddress(add, false)); + }); return alertDialog; } + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(GROUP, mGroup); + } + + private void updateAddress(final AddressTypes addressType) { + if (addressType == VIRTUAL_ADDRESS) { + labelSummary.setVisibility(VISIBLE); + labelUuidView.setVisibility(VISIBLE); + mGenerateLabelUUID.setVisibility(VISIBLE); + groupNameInputLayout.setEnabled(true); + groupNameInputLayout.setError(null); + final UUID uuid = UUID.fromString(labelUuidView.getText().toString()); + final Integer add = MeshAddress.generateVirtualAddress(uuid); + addressInput.setText(String.valueOf(add)); + addressInputLayout.setError(null); + addressInputLayout.setEnabled(false); + } else { + labelSummary.setVisibility(GONE); + labelUuidView.setVisibility(GONE); + mGenerateLabelUUID.setVisibility(GONE); + updateGroup(); + } + } + + private void updateGroup() { + if (mGroup == null) { + mGroup = ((GroupCallbacks) requireParentFragment()).createGroup(); + } + groupNameInput.setText(mGroup.getName()); + addressInput.setText(MeshAddress.formatAddress(mGroup.getAddress(), false)); + } + private boolean validateInput(@NonNull final String name, @NonNull final String address) { try { - if(TextUtils.isEmpty(name)){ + if (TextUtils.isEmpty(name)) { groupNameInputLayout.setError(getString(R.string.error_empty_group_name)); return false; } @@ -140,7 +263,7 @@ private boolean validateInput(@NonNull final String name, @NonNull final String } final byte[] groupAddress = MeshParserUtils.toByteArray(address); - if(!MeshParserUtils.isValidSubscriptionAddress(groupAddress)){ + if (!MeshAddress.isValidSubscriptionAddress(groupAddress)) { addressInputLayout.setError(getString(R.string.invalid_address_value)); return false; } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentDeleteNode.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentDeleteNode.java new file mode 100644 index 000000000..660776df0 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentDeleteNode.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.dialog; + +import android.app.Dialog; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; + +public class DialogFragmentDeleteNode extends DialogFragmentMessage { + + private int position; + + public interface DialogFragmentDeleteNodeListener { + void onNodeDeleteConfirmed(final int position); + + void onNodeDeleteCancelled(final int position); + } + + public static DialogFragmentDeleteNode newInstance(final int position) { + Bundle args = new Bundle(); + DialogFragmentDeleteNode fragment = new DialogFragmentDeleteNode(); + args.putInt(Utils.EXTRA_DATA, position); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + position = getArguments().getInt(Utils.EXTRA_DATA, -1); + } + title = getString(R.string.title_delete_node); + message = getString(R.string.delete_node_rationale); + + if (savedInstanceState != null) { + position = savedInstanceState.getInt(Utils.EXTRA_DATA, -1); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + alertDialogBuilder = new AlertDialog.Builder(requireActivity()); + alertDialogBuilder.setIcon(R.drawable.ic_delete_black_alpha_24dp); + alertDialogBuilder.setNegativeButton(getString(R.string.no), (dialog, which) -> + ((DialogFragmentDeleteNodeListener) requireParentFragment()).onNodeDeleteCancelled(position)); + alertDialogBuilder.setPositiveButton(getString(R.string.yes), (dialog, which) -> + ((DialogFragmentDeleteNodeListener) requireParentFragment()).onNodeDeleteConfirmed(position)); + + return super.onCreateDialog(savedInstanceState); + } + + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(Utils.EXTRA_DATA, position); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentDisconnected.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentDisconnected.java index d827679e9..6ef726417 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentDisconnected.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentDisconnected.java @@ -24,8 +24,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigError.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentError.java similarity index 87% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigError.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentError.java index 154ff59ba..64db668ba 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentConfigError.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentError.java @@ -24,16 +24,16 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; -public class DialogFragmentConfigError extends DialogFragmentMessage { +public class DialogFragmentError extends DialogFragmentMessage { - public static DialogFragmentConfigError newInstance(final String title, final String message) { + public static DialogFragmentError newInstance(@NonNull final String title, @NonNull final String message) { Bundle args = new Bundle(); - DialogFragmentConfigError fragment = new DialogFragmentConfigError(); + DialogFragmentError fragment = new DialogFragmentError(); args.putString(TITLE, title); args.putString(MESSAGE, message); fragment.setArguments(args); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentFilterAddAddress.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentFilterAddAddress.java index 196069ed9..e421c615b 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentFilterAddAddress.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentFilterAddAddress.java @@ -22,31 +22,35 @@ package no.nordicsemi.android.nrfmeshprovisioner.dialog; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.text.Editable; import android.text.TextWatcher; import android.text.method.KeyListener; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; +import android.widget.TextView; import android.widget.Toast; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + import java.util.ArrayList; import java.util.List; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.utils.AddressArray; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.meshprovisioner.utils.ProxyFilterType; import no.nordicsemi.android.nrfmeshprovisioner.R; @@ -91,11 +95,11 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_filter_address, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_filter_address, null); //Bind ui ButterKnife.bind(this, rootView); - + final TextView summary = rootView.findViewById(R.id.summary); if (savedInstanceState != null) { filterType = savedInstanceState.getParcelable(PROXY_FILTER_KEY); addresses = savedInstanceState.getParcelableArrayList("AddressList"); @@ -109,7 +113,7 @@ public Dialog onCreateDialog(final Bundle savedInstanceState) { final Button actionAdd = rootView.findViewById(R.id.action_add); actionAdd.setOnClickListener(v -> { - final String addressVal = addressInput.getEditableText().toString(); + final String addressVal = addressInput.getEditableText().toString().trim(); if (validateInput(addressVal)) { addressInput.getEditableText().clear(); final byte[] address = MeshParserUtils.toByteArray(addressVal); @@ -138,22 +142,19 @@ public void afterTextChanged(final Editable s) { } }); - return new AlertDialog.Builder(getContext()).setView(rootView) + summary.setText(getString(R.string.dialog_summary_filter_address, filterType.getFilterTypeName())); + + return new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.confirm, (dialog, which) -> { if (!addresses.isEmpty()) { - if (getParentFragment() == null) { - ((DialogFragmentFilterAddressListener) requireActivity()).addAddresses(addresses); - } else { - ((DialogFragmentFilterAddressListener) getParentFragment()).addAddresses(addresses); - } + ((DialogFragmentFilterAddressListener) requireParentFragment()).addAddresses(addresses); } else { Toast.makeText(requireContext(), R.string.error_empty_filter_address, Toast.LENGTH_SHORT).show(); } }) .setNegativeButton(R.string.cancel, null) .setIcon(R.drawable.ic_lan_black_alpha_24dp) - .setTitle(R.string.title_add_address) - .setMessage(getString(R.string.dialog_summary_filter_address, filterType.getFilterTypeName())).create(); + .setTitle(R.string.title_add_address).create(); } @Override @@ -164,16 +165,14 @@ public void onSaveInstanceState(@NonNull final Bundle outState) { } private boolean validateInput(final String input) { - try { - if (input.length() % 4 != 0 || !input.matches(Utils.HEX_PATTERN)) { addressInputLayout.setError(getString(R.string.invalid_address_value)); return false; } final byte[] address = MeshParserUtils.toByteArray(input); - if (!MeshParserUtils.isValidUnicastAddress(address) && !MeshParserUtils.isValidFilterAddress(address)) { + if (!MeshAddress.isValidProxyFilterAddress(address)) { addressInputLayout.setError(getString(R.string.invalid_filter_address)); return false; } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentFlags.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentFlags.java deleted file mode 100644 index 0e889874b..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentFlags.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2018, Nordic Semiconductor - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package no.nordicsemi.android.nrfmeshprovisioner.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.DialogFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.RadioButton; -import android.widget.RadioGroup; - -import butterknife.BindView; -import butterknife.ButterKnife; -import no.nordicsemi.android.nrfmeshprovisioner.R; - - -public class DialogFragmentFlags extends DialogFragment { - - private static final String KEY_REFRESH_FLAG = "KEY_REFRESH_FLAG"; - private static final String IV_UPDATE_FLAG = "IV_UPDATE_FLAG"; - //UI Bindings - @BindView(R.id.radio_group_refresh_flag) - RadioGroup radioGroupKeyRefreshFlag; - @BindView(R.id.radio_group_iv_update_flag) - RadioGroup radioGroupIVUpdateFlag; - @BindView(R.id.radio_key_refresh_phase_0) - RadioButton radioKeyRefreshPhase0; - @BindView(R.id.radio_key_refresh_phase_2) - RadioButton radioKeyRefreshPhase2; - @BindView(R.id.radio_normal_operation) - RadioButton radioNormalOperation; - @BindView(R.id.radio_iv_update_active) - RadioButton radioIvUpdate; - - private int mKeyRefreshFlag; - private int mIvUpdateFlag; - - public static DialogFragmentFlags newInstance(final int keyRefreshFlag, final int ivUpdateFlag) { - DialogFragmentFlags fragmentNetworkKey = new DialogFragmentFlags(); - final Bundle args = new Bundle(); - args.putInt(KEY_REFRESH_FLAG, keyRefreshFlag); - args.putInt(IV_UPDATE_FLAG, ivUpdateFlag); - fragmentNetworkKey.setArguments(args); - return fragmentNetworkKey; - } - - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - mKeyRefreshFlag = getArguments().getInt(KEY_REFRESH_FLAG); - mIvUpdateFlag = getArguments().getInt(IV_UPDATE_FLAG); - } - } - - @NonNull - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_flags_input, null); - - //Bind ui - ButterKnife.bind(this, rootView); - - if (mKeyRefreshFlag == 0) - radioKeyRefreshPhase0.setChecked(true); - else - radioKeyRefreshPhase2.setChecked(true); - - if (mIvUpdateFlag == 0) - radioNormalOperation.setChecked(true); - else - radioIvUpdate.setChecked(true); - - - radioGroupKeyRefreshFlag.setEnabled(false); - radioGroupIVUpdateFlag.setEnabled(false); - - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); - - alertDialogBuilder.setIcon(R.drawable.ic_flag); - alertDialogBuilder.setTitle(R.string.title_flags); - alertDialogBuilder.setMessage(R.string.dialog_summary_flags); - - final AlertDialog alertDialog = alertDialogBuilder.show(); - alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - if (validateInput()) { - final int keyRefreshFlag = parseKeyRefreshFlag(); - final int ivUpdateFlag = parseIvUpdateFlag(); - if (getParentFragment() == null) { - ((DialogFragmentFlagsListener) getActivity()).onFlagsSelected(keyRefreshFlag, ivUpdateFlag); - } else { - ((DialogFragmentFlagsListener) getParentFragment()).onFlagsSelected(keyRefreshFlag, ivUpdateFlag); - } - dismiss(); - } - }); - - return alertDialog; - } - - private boolean validateInput() { - - int refreshFlagId = radioGroupKeyRefreshFlag.getCheckedRadioButtonId(); - switch (refreshFlagId) { - case R.id.radio_key_refresh_phase_0: - case R.id.radio_key_refresh_phase_2: - break; - default: - return false; - } - - int updateFlagId = radioGroupIVUpdateFlag.getCheckedRadioButtonId(); - switch (updateFlagId) { - case R.id.radio_normal_operation: - case R.id.radio_iv_update_active: - break; - default: - return false; - } - return true; - } - - private int parseKeyRefreshFlag() { - int refreshFlagId = radioGroupKeyRefreshFlag.getCheckedRadioButtonId(); - switch (refreshFlagId) { - default: - case R.id.radio_key_refresh_phase_0: - return 0; - case R.id.radio_key_refresh_phase_2: - return 1; - } - } - - private int parseIvUpdateFlag() { - int refreshFlagId = radioGroupIVUpdateFlag.getCheckedRadioButtonId(); - switch (refreshFlagId) { - default: - case R.id.radio_normal_operation: - return 0; - case R.id.radio_iv_update_active: - return 1; - } - } - - public interface DialogFragmentFlagsListener { - - void onFlagsSelected(final int keyRefreshFlag, final int ivUpdateFlag); - - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGroupSubscription.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGroupSubscription.java index 9d4d43410..e936c2d38 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGroupSubscription.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGroupSubscription.java @@ -22,41 +22,60 @@ package no.nordicsemi.android.nrfmeshprovisioner.dialog; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.KeyListener; import android.view.LayoutInflater; import android.view.View; +import android.widget.AdapterView; +import android.widget.Button; import android.widget.RadioButton; import android.widget.Spinner; import android.widget.TextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + import java.util.ArrayList; +import java.util.Locale; +import java.util.UUID; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.Group; import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.GroupCallbacks; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.adapter.AddressTypeAdapter; import no.nordicsemi.android.nrfmeshprovisioner.adapter.GroupAdapterSpinner; +import no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes; import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.GROUP_ADDRESS; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.VIRTUAL_ADDRESS; public class DialogFragmentGroupSubscription extends DialogFragment { + private static final AddressTypes[] addressTypes = {GROUP_ADDRESS, VIRTUAL_ADDRESS}; private static final String GROUPS = "GROUPS"; + private static final String GROUP = "GROUP"; //UI Bindings + @BindView(R.id.address_types) + Spinner addressTypesSpinnerView; + @BindView(R.id.group_container) + View groupContainer; @BindView(R.id.radio_select_group) RadioButton selectGroup; @BindView(R.id.radio_create_group) @@ -73,16 +92,17 @@ public class DialogFragmentGroupSubscription extends DialogFragment { TextInputEditText addressInput; @BindView(R.id.no_groups_configured) TextView noGroups; + @BindView(R.id.label_summary) + TextView labelSummary; + @BindView(R.id.uuid_label) + TextView labelUuidView; - private ArrayList mGroups; - + private Button mGenerateLabelUUID; - public interface DialogFragmentSubscriptionAddressListener { + private AddressTypeAdapter mAdapterSpinner; - void setGroupSubscription(@NonNull final String name, final int address); - void setGroupSubscription(@NonNull final Group group); - - } + private ArrayList mGroups; + private Group mGroup; public static DialogFragmentGroupSubscription newInstance(final ArrayList groups) { final DialogFragmentGroupSubscription fragment = new DialogFragmentGroupSubscription(); @@ -103,10 +123,16 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_group_subscription, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()). + inflate(R.layout.dialog_fragment_group_subscription, null); //Bind ui ButterKnife.bind(this, rootView); + if (savedInstanceState == null) { + mGroup = ((GroupCallbacks) requireActivity()).createGroup(); + } else { + mGroup = savedInstanceState.getParcelable(GROUP); + } selectGroup.setOnCheckedChangeListener((buttonView, isChecked) -> { groupNameInputLayout.setEnabled(!isChecked); @@ -124,12 +150,21 @@ public Dialog onCreateDialog(final Bundle savedInstanceState) { addressInputLayout.setError(null); groups.setEnabled(!isChecked); selectGroup.setChecked(!isChecked); + if (isChecked) { + if (mGroup != null) { + groupNameInput.setText(mGroup.getName()); + addressInput.setText(MeshAddress.formatAddress(mGroup.getAddress(), false)); + } + } }); + mAdapterSpinner = new AddressTypeAdapter(requireContext(), addressTypes); + addressTypesSpinnerView.setAdapter(mAdapterSpinner); + final GroupAdapterSpinner adapter = new GroupAdapterSpinner(requireContext(), mGroups); groups.setAdapter(adapter); - if(mGroups.isEmpty()){ + if (mGroups.isEmpty()) { selectGroup.setEnabled(false); groups.setEnabled(false); createGroup.setChecked(true); @@ -138,6 +173,20 @@ public Dialog onCreateDialog(final Bundle savedInstanceState) { createGroup.setChecked(false); } + updateGroup(); + + addressTypesSpinnerView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(final AdapterView parent, final View view, final int position, final long id) { + updateAddress(mAdapterSpinner.getItem(position)); + } + + @Override + public void onNothingSelected(final AdapterView parent) { + + } + }); + final KeyListener hexKeyListener = new HexKeyListener(); addressInput.setKeyListener(hexKeyListener); addressInput.addTextChangedListener(new TextWatcher() { @@ -148,6 +197,7 @@ public void beforeTextChanged(final CharSequence s, final int start, final int c @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + mGroup = null; if (TextUtils.isEmpty(s.toString())) { addressInputLayout.setError(getString(R.string.error_empty_group_address)); } else { @@ -161,34 +211,109 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); - - alertDialogBuilder.setIcon(R.drawable.ic_subscribe_black_alpha_24dp); - alertDialogBuilder.setTitle(R.string.title_subscribe_group); + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()) + .setIcon(R.drawable.ic_subscribe_black_alpha_24dp) + .setTitle(R.string.title_subscribe_group) + .setView(rootView) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .setNeutralButton(R.string.generate_uuid, null); final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - if(createGroup.isChecked()) { - final String name = groupNameInput.getEditableText().toString(); - final String address = addressInput.getEditableText().toString(); - if (validateInput(name, address)) { - ((DialogFragmentSubscriptionAddressListener) requireActivity()).setGroupSubscription(name, Integer.valueOf(address, 16)); - dismiss(); + final AddressTypes type = (AddressTypes) addressTypesSpinnerView.getSelectedItem(); + try { + if (type == GROUP_ADDRESS) { + if (createGroup.isChecked()) { + if (mGroup != null) { + if (((GroupCallbacks) requireActivity()).onGroupAdded(mGroup)) { + dismiss(); + } + } else { + final String name = groupNameInput.getEditableText().toString().trim(); + final String address = addressInput.getEditableText().toString().trim(); + if (validateInput(name, address)) { + if ((((GroupCallbacks) requireActivity())). + onGroupAdded(name, Integer.valueOf(address, 16))) { + dismiss(); + } + } + } + } else { + final Group group = (Group) groups.getSelectedItem(); + ((GroupCallbacks) requireActivity()).subscribe(group); + dismiss(); + } + } else { + final UUID uuid = UUID.fromString(labelUuidView.getText().toString().trim()); + final String name = groupNameInput.getEditableText().toString().trim(); + final Group group = ((GroupCallbacks) requireActivity()).createGroup(uuid, name); + if (group != null) { + if (((GroupCallbacks) requireActivity()).onGroupAdded(group)) { + dismiss(); + } + } } - } else { - final Group group = (Group) groups.getSelectedItem(); - ((DialogFragmentSubscriptionAddressListener) requireActivity()).setGroupSubscription(group); - dismiss(); + } catch (IllegalArgumentException ex) { + addressInputLayout.setError(ex.getMessage()); } }); + mGenerateLabelUUID = alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL); + mGenerateLabelUUID.setOnClickListener(v -> { + final UUID uuid = MeshAddress.generateRandomLabelUUID(); + labelUuidView.setText(uuid.toString().toUpperCase(Locale.US)); + generateVirtualAddress(uuid); + }); + return alertDialog; } + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(GROUP, mGroup); + } + + private void updateAddress(@NonNull final AddressTypes addressType) { + if (addressType == VIRTUAL_ADDRESS) { + labelSummary.setVisibility(VISIBLE); + labelUuidView.setVisibility(VISIBLE); + mGenerateLabelUUID.setVisibility(VISIBLE); + groupContainer.setVisibility(GONE); + groupNameInputLayout.setEnabled(true); + groupNameInputLayout.setError(null); + addressInputLayout.setError(null); + addressInputLayout.setEnabled(false); + generateVirtualAddress(UUID.fromString(labelUuidView.getText().toString())); + } else { + groupContainer.setVisibility(VISIBLE); + labelSummary.setVisibility(GONE); + labelUuidView.setVisibility(GONE); + mGenerateLabelUUID.setVisibility(GONE); + updateGroup(); + } + } + + private void generateVirtualAddress(@NonNull final UUID uuid) { + final Integer add = MeshAddress.generateVirtualAddress(uuid); + addressInput.setText(MeshAddress.formatAddress(add, false)); + } + + private void updateGroup() { + if (mGroup == null) { + mGroup = ((GroupCallbacks) requireActivity()).createGroup(); + } + + if (mGroup != null) { + groupNameInput.setText(mGroup.getName()); + addressInput.setText(MeshAddress.formatAddress(mGroup.getAddress(), false)); + } + } + private boolean validateInput(@NonNull final String name, @NonNull final String address) { try { - if(TextUtils.isEmpty(name)){ + if (TextUtils.isEmpty(name)) { groupNameInputLayout.setError(getString(R.string.error_empty_group_name)); return false; } @@ -198,13 +323,13 @@ private boolean validateInput(@NonNull final String name, @NonNull final String } final int groupAddress = Integer.valueOf(address, 16); - if(!MeshAddress.isValidGroupAddress(groupAddress)){ + if (!MeshAddress.isValidGroupAddress(groupAddress)) { addressInputLayout.setError(getString(R.string.invalid_address_value)); return false; } - for(Group group : mGroups) { - if(groupAddress == group.getGroupAddress()){ + for (Group group : mGroups) { + if (groupAddress == group.getAddress()) { addressInputLayout.setError(getString(R.string.error_group_address_in_used)); return false; } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentIvIndex.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentIvIndex.java deleted file mode 100644 index 59c9bcc13..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentIvIndex.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2018, Nordic Semiconductor - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package no.nordicsemi.android.nrfmeshprovisioner.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.method.KeyListener; -import android.view.LayoutInflater; -import android.view.View; - -import java.util.Locale; - -import butterknife.BindView; -import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.nrfmeshprovisioner.R; -import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; - - -public class DialogFragmentIvIndex extends DialogFragment { - - private static final String IV_INDEX = "IV_INDEX"; - //UI Bindings - @BindView(R.id.text_input_layout) - TextInputLayout ivIndexInputLayout; - @BindView(R.id.text_input) - TextInputEditText ivIndexInput; - - private int mIvIndex; - - public static DialogFragmentIvIndex newInstance(final int ivIndex) { - DialogFragmentIvIndex fragmentIvIndex = new DialogFragmentIvIndex(); - final Bundle args = new Bundle(); - args.putInt(IV_INDEX, ivIndex); - fragmentIvIndex.setArguments(args); - return fragmentIvIndex; - } - - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - mIvIndex = getArguments().getInt(IV_INDEX); - } - } - - @NonNull - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_ivindex_input, null); - - //Bind ui - ButterKnife.bind(this, rootView); - - final KeyListener hexKeyListener = new HexKeyListener(); - final String ivIndex = String.format(Locale.US, "%08X", mIvIndex); - ivIndexInputLayout.setHint(getString(R.string.hint_iv_index)); - ivIndexInput.setText(ivIndex); - ivIndexInput.setKeyListener(hexKeyListener); - ivIndexInput.setSelection(ivIndex.length()); - ivIndexInput.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { - - } - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { - if (TextUtils.isEmpty(s.toString())) { - ivIndexInputLayout.setError(getString(R.string.error_empty_iv_index)); - } else { - ivIndexInputLayout.setError(null); - } - } - - @Override - public void afterTextChanged(final Editable s) { - - } - }); - - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); - - alertDialogBuilder.setIcon(R.drawable.ic_list); - alertDialogBuilder.setTitle(R.string.title_iv_index); - alertDialogBuilder.setMessage(R.string.dialog_summary_iv_index); - - final AlertDialog alertDialog = alertDialogBuilder.show(); - alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String ivIndexInput = this.ivIndexInput.getText().toString(); - if (validateInput(ivIndexInput)) { - if (getParentFragment() == null) { - ((DialogFragmentIvIndexListener) getActivity()).setIvIndex(Integer.parseInt(ivIndexInput, 16)); - } else { - ((DialogFragmentIvIndexListener) getParentFragment()).setIvIndex(Integer.parseInt(ivIndexInput, 16)); - } - dismiss(); - } - }); - - return alertDialog; - } - - private boolean validateInput(final String input) { - - try { - - if(!input.matches(Utils.HEX_PATTERN)) { - ivIndexInputLayout.setError(getString(R.string.invalid_hex_value)); - return false; - } - if (MeshParserUtils.validateIvIndexInput(getContext(), input)) { - return true; - } - } catch (IllegalArgumentException ex) { - ivIndexInputLayout.setError(ex.getMessage()); - } - - return false; - } - - public interface DialogFragmentIvIndexListener { - - void setIvIndex(final int ivIndex); - - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentKeyIndex.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentKeyIndex.java deleted file mode 100644 index 77fc8fe9e..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentKeyIndex.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2018, Nordic Semiconductor - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package no.nordicsemi.android.nrfmeshprovisioner.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.method.KeyListener; -import android.view.LayoutInflater; -import android.view.View; - -import java.util.Locale; - -import butterknife.BindView; -import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.nrfmeshprovisioner.R; -import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; - - -public class DialogFragmentKeyIndex extends DialogFragment { - - private static final String KEY_INDEX = "KEY_INDEX"; - //UI Bindings - @BindView(R.id.text_input_layout) - TextInputLayout keyIndexInputLayout; - @BindView(R.id.text_input) - TextInputEditText keyIndexInput; - - private int mKeyIndex; - - public static DialogFragmentKeyIndex newInstance(final int networkKeyIndex) { - DialogFragmentKeyIndex fragmentNetworkKey = new DialogFragmentKeyIndex(); - final Bundle args = new Bundle(); - args.putInt(KEY_INDEX, networkKeyIndex); - fragmentNetworkKey.setArguments(args); - return fragmentNetworkKey; - } - - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - mKeyIndex = getArguments().getInt(KEY_INDEX); - } - } - - @NonNull - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_key_index_input, null); - - //Bind ui - ButterKnife.bind(this, rootView); - - final KeyListener hexKeyListener = new HexKeyListener(); - keyIndexInputLayout.setHint(getString((R.string.hint_key_index))); - final String netKeyIndex = String.format(Locale.US, "%03X", mKeyIndex); - keyIndexInput.setKeyListener(hexKeyListener); - keyIndexInput.setText(netKeyIndex); - keyIndexInput.setSelection(netKeyIndex.length()); - keyIndexInput.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { - - } - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { - if (TextUtils.isEmpty(s.toString())) { - keyIndexInputLayout.setError(getString(R.string.error_empty_key_index)); - } else { - keyIndexInputLayout.setError(null); - } - } - - @Override - public void afterTextChanged(final Editable s) { - - } - }); - - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); - - alertDialogBuilder.setIcon(R.drawable.ic_numeric); - alertDialogBuilder.setTitle(R.string.title_key_index); - alertDialogBuilder.setMessage(R.string.dialog_summary_key_index); - - final AlertDialog alertDialog = alertDialogBuilder.show(); - alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String keyIndex = keyIndexInput.getText().toString(); - if (validateInput(keyIndex)) { - if ((getParentFragment()) == null) { - ((DialogFragmentKeyIndexListener) getActivity()).onKeyIndexGenerated(Integer.valueOf(keyIndex)); - } else { - ((DialogFragmentKeyIndexListener) getParentFragment()).onKeyIndexGenerated(Integer.valueOf(keyIndex)); - } - dismiss(); - } - }); - - return alertDialog; - } - - private boolean validateInput(final String input) { - try { - - if(!input.matches(Utils.HEX_PATTERN)) { - keyIndexInputLayout.setError(getString(R.string.invalid_hex_value)); - return false; - } - - if (MeshParserUtils.validateKeyIndexInput(getContext(), input)) { - return true; - } - } catch (IllegalArgumentException ex) { - keyIndexInputLayout.setError(ex.getMessage()); - } - return false; - } - - public interface DialogFragmentKeyIndexListener { - - void onKeyIndexGenerated(final int keyIndex); - - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshExportMsg.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshExportMsg.java index 22b418298..6fe40272f 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshExportMsg.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshExportMsg.java @@ -24,8 +24,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshImport.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshImport.java index dd8501df8..f5cc8a215 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshImport.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshImport.java @@ -24,8 +24,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshImportMsg.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshImportMsg.java index d7588acc8..64482d6cd 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshImportMsg.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMeshImportMsg.java @@ -24,8 +24,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMessage.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMessage.java index d0e6cc660..3a6256e71 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMessage.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentMessage.java @@ -25,13 +25,13 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; public class DialogFragmentMessage extends DialogFragment { - protected static final String ICON_RES_ID = "ICON_RES_ID"; + static final String ICON_RES_ID = "ICON_RES_ID"; protected static final String TITLE = "TITLE"; protected static final String MESSAGE = "MESSAGE"; protected AlertDialog.Builder alertDialogBuilder; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGlobalNetworkName.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNetworkName.java similarity index 78% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGlobalNetworkName.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNetworkName.java index 95e6fffdf..ad36ee939 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGlobalNetworkName.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNetworkName.java @@ -22,26 +22,29 @@ package no.nordicsemi.android.nrfmeshprovisioner.dialog; -import android.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; + +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import androidx.fragment.app.DialogFragment; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; +import android.widget.TextView; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.nrfmeshprovisioner.R; -public class DialogFragmentGlobalNetworkName extends DialogFragment { +public class DialogFragmentNetworkName extends DialogFragment { private static final String NETWORK_NAME = "NETWORK_NAME"; @@ -53,8 +56,8 @@ public class DialogFragmentGlobalNetworkName extends DialogFragment { private String mNetworkName; - public static DialogFragmentGlobalNetworkName newInstance(final String networkName) { - DialogFragmentGlobalNetworkName fragmentNetworkKey = new DialogFragmentGlobalNetworkName(); + public static DialogFragmentNetworkName newInstance(final String networkName) { + DialogFragmentNetworkName fragmentNetworkKey = new DialogFragmentNetworkName(); final Bundle args = new Bundle(); args.putString(NETWORK_NAME, networkName); fragmentNetworkKey.setArguments(args); @@ -72,10 +75,12 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_name, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater + .from(getContext()).inflate(R.layout.dialog_fragment_name, null); //Bind ui ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); networkNameInputLayout.setHint(getString(R.string.hint_global_network_name)); networkNameInput.setText(mNetworkName); networkNameInput.addTextChangedListener(new TextWatcher() { @@ -87,7 +92,7 @@ public void beforeTextChanged(final CharSequence s, final int start, final int c @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { if (TextUtils.isEmpty(s.toString())) { - networkNameInputLayout.setError(getString(R.string.error_empty_network_key)); + networkNameInputLayout.setError(getString(R.string.error_empty_name)); } else { networkNameInputLayout.setError(null); } @@ -99,19 +104,19 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); - alertDialogBuilder.setIcon(R.drawable.ic_lan_black_alpha_24dp); + alertDialogBuilder.setIcon(R.drawable.ic_label_black_alpha_24dp); alertDialogBuilder.setTitle(R.string.title_network_name); - alertDialogBuilder.setMessage(R.string.summary_network_name); + summary.setText(R.string.summary_network_name); final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String networkKey = networkNameInput.getText().toString(); + final String networkKey = networkNameInput.getEditableText().toString().trim(); if (validateInput(networkKey)) { if(getParentFragment() == null) { - ((DialogFragmentNetworkNameListener) getActivity()).onNetworkNameEntered(networkKey); + ((DialogFragmentNetworkNameListener) requireActivity()).onNetworkNameEntered(networkKey); } else { ((DialogFragmentNetworkNameListener) getParentFragment()).onNetworkNameEntered(networkKey); } @@ -124,7 +129,7 @@ public void afterTextChanged(final Editable s) { private boolean validateInput(final String input) { if(TextUtils.isEmpty(input)){ - networkNameInputLayout.setError(getString(R.string.error_empty_network_name)); + networkNameInputLayout.setError(getString(R.string.error_empty_name)); return false; } @@ -133,7 +138,7 @@ private boolean validateInput(final String input) { public interface DialogFragmentNetworkNameListener { - void onNetworkNameEntered(final String networkKey); + void onNetworkNameEntered(@NonNull final String name); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPermissionRationale.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPermissionRationale.java index 2aa1426a8..40d9347a3 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPermissionRationale.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPermissionRationale.java @@ -27,8 +27,8 @@ import android.net.Uri; import android.os.Bundle; import android.provider.Settings; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentProvisioningFailedError.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentProvisioningFailedError.java index e82cf5d12..af86f7b85 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentProvisioningFailedError.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentProvisioningFailedError.java @@ -24,8 +24,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentProxySet.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentProxySet.java index f679803d4..8a5ccc4bc 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentProxySet.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentProxySet.java @@ -24,8 +24,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.meshprovisioner.transport.ConfigProxySet; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublishAddress.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublishAddress.java deleted file mode 100644 index 21083b41c..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublishAddress.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2018, Nordic Semiconductor - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package no.nordicsemi.android.nrfmeshprovisioner.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.method.KeyListener; -import android.view.LayoutInflater; -import android.view.View; - -import butterknife.BindView; -import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.nrfmeshprovisioner.R; -import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; - - -public class DialogFragmentPublishAddress extends DialogFragment { - - private static final String PUBLISH_ADDRESS = "PUBLISH_ADDRESS"; - private byte[] mPublishAddress; - - //UI Bindings - @BindView(R.id.text_input_layout) - TextInputLayout unicastAddressInputLayout; - @BindView(R.id.text_input) - TextInputEditText unicastAddressInput; - - public static DialogFragmentPublishAddress newInstance(final byte[] publishAddress) { - DialogFragmentPublishAddress fragmentPublishAddress = new DialogFragmentPublishAddress(); - final Bundle args = new Bundle(); - args.putByteArray(PUBLISH_ADDRESS, publishAddress); - fragmentPublishAddress.setArguments(args); - return fragmentPublishAddress; - } - - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if(getArguments() != null) { - mPublishAddress = getArguments().getByteArray(PUBLISH_ADDRESS); - } - } - - @NonNull - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_address_input, null); - - //Bind ui - ButterKnife.bind(this, rootView); - final String publishAddress; - if(mPublishAddress != null) { - publishAddress = MeshParserUtils.bytesToHex(mPublishAddress, false); - unicastAddressInput.setText(publishAddress); - } - - final KeyListener hexKeyListener = new HexKeyListener(); - unicastAddressInputLayout.setHint(getString((R.string.hint_publish_address))); - unicastAddressInput.setKeyListener(hexKeyListener); - unicastAddressInput.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { - - } - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { - if (TextUtils.isEmpty(s.toString())) { - unicastAddressInputLayout.setError(getString(R.string.error_empty_publish_address)); - } else { - unicastAddressInputLayout.setError(null); - } - } - - @Override - public void afterTextChanged(final Editable s) { - - } - }); - - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); - - alertDialogBuilder.setIcon(R.drawable.ic_lan_black_alpha_24dp); - alertDialogBuilder.setTitle(R.string.title_publish_address); - alertDialogBuilder.setMessage(R.string.dialog_summary_publish_address); - - final AlertDialog alertDialog = alertDialogBuilder.show(); - alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String pubAddress = unicastAddressInput.getText().toString(); - if (validateInput(pubAddress)) { - if (getParentFragment() == null) { - ((DialogFragmentPublishAddressListener) getActivity()).setPublishAddress(MeshParserUtils.toByteArray(pubAddress)); - } else { - ((DialogFragmentPublishAddressListener) getParentFragment()).setPublishAddress(MeshParserUtils.toByteArray(pubAddress)); - } - dismiss(); - } - }); - - return alertDialog; - } - - private boolean validateInput(final String input) { - - try { - - if(input.length() % 4 != 0 || !input.matches(Utils.HEX_PATTERN)) { - unicastAddressInputLayout.setError(getString(R.string.invalid_address_value)); - return false; - } - } catch (IllegalArgumentException ex) { - unicastAddressInputLayout.setError(ex.getMessage()); - return false; - } - - return true; - } - - public interface DialogFragmentPublishAddressListener { - - void setPublishAddress(final byte[] publishAddress); - - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentResetNetwork.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentResetNetwork.java index 11e91caf6..fc29e2f7c 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentResetNetwork.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentResetNetwork.java @@ -24,8 +24,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentSelectOOBType.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentSelectOOBType.java index 28ce8d7f6..5f7992cab 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentSelectOOBType.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentSelectOOBType.java @@ -22,12 +22,14 @@ package no.nordicsemi.android.nrfmeshprovisioner.dialog; -import android.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; + +import android.annotation.SuppressLint; import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; @@ -114,7 +116,7 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_select_oob_type, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_select_oob_type, null); //Bind ui ButterKnife.bind(this, rootView); @@ -162,7 +164,7 @@ public void onNothingSelected(final AdapterView parent) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()). + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()). setView(rootView). setIcon(R.drawable.ic_oob_lock_outline). setTitle(R.string.title_select_oob). diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentTransactionStatus.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentTransactionStatus.java index a25a2085f..57b40d8e5 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentTransactionStatus.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentTransactionStatus.java @@ -24,8 +24,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentUnicastAddress.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentUnicastAddress.java index 63d6099dc..fbc2787de 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentUnicastAddress.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentUnicastAddress.java @@ -22,24 +22,25 @@ package no.nordicsemi.android.nrfmeshprovisioner.dialog; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.KeyListener; import android.view.LayoutInflater; import android.view.View; +import android.widget.TextView; -import java.util.Locale; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; @@ -51,6 +52,8 @@ public class DialogFragmentUnicastAddress extends DialogFragment { private static final String UNICAST_ADDRESS = "UNICAST_ADDRESS"; + private static final String ELEMENT_COUNT = "ELEMENT_COUNT"; + //UI Bindings @BindView(R.id.text_input_layout) TextInputLayout unicastAddressInputLayout; @@ -58,11 +61,13 @@ public class DialogFragmentUnicastAddress extends DialogFragment { TextInputEditText unicastAddressInput; private int mUnicastAddress; + private int mElementCount; - public static DialogFragmentUnicastAddress newInstance(final int unicastAddress) { + public static DialogFragmentUnicastAddress newInstance(final int unicastAddress, final int elementCount) { DialogFragmentUnicastAddress fragmentIvIndex = new DialogFragmentUnicastAddress(); final Bundle args = new Bundle(); args.putInt(UNICAST_ADDRESS, unicastAddress); + args.putInt(ELEMENT_COUNT, elementCount); fragmentIvIndex.setArguments(args); return fragmentIvIndex; } @@ -72,19 +77,20 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mUnicastAddress = getArguments().getInt(UNICAST_ADDRESS); + mElementCount = getArguments().getInt(ELEMENT_COUNT); } } @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_address_input, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_address_input, null); //Bind ui ButterKnife.bind(this, rootView); - + final TextView summary = rootView.findViewById(R.id.summary); final KeyListener hexKeyListener = new HexKeyListener(); - final String unicastAddress = String.format(Locale.US, "%04X", mUnicastAddress); + final String unicastAddress = MeshAddress.formatAddress(mUnicastAddress, false); unicastAddressInputLayout.setHint(getString((R.string.hint_unicast_address))); unicastAddressInput.setText(unicastAddress); unicastAddressInput.setSelection(unicastAddress.length()); @@ -110,23 +116,36 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()) + .setIcon(R.drawable.ic_lan_black_alpha_24dp) + .setTitle(R.string.title_unicast_address) + .setView(rootView) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .setNeutralButton(R.string.automatic, null); - alertDialogBuilder.setIcon(R.drawable.ic_lan_black_alpha_24dp); - alertDialogBuilder.setTitle(R.string.title_unicast_address); - alertDialogBuilder.setMessage(R.string.dialog_summary_unicast_address); + summary.setText(R.string.dialog_summary_unicast_address); final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String unicast = unicastAddressInput.getEditableText().toString(); - if (validateInput(unicast)) { - if (getParentFragment() == null) { - ((DialogFragmentUnicastAddressListener) getActivity()).setUnicastAddress(Integer.parseInt(unicast, 16)); - } else { - ((DialogFragmentUnicastAddressListener) getParentFragment()).setUnicastAddress(Integer.parseInt(unicast, 16)); + final String unicast = unicastAddressInput.getEditableText().toString().trim(); + try { + if (validateInput(unicast)) { + if (((DialogFragmentUnicastAddressListener) requireActivity()).setUnicastAddress(Integer.parseInt(unicast, 16))) { + dismiss(); + } } - dismiss(); + } catch (IllegalArgumentException ex) { + unicastAddressInputLayout.setError(ex.getMessage()); + } + }); + + alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> { + try { + final int unicast = ((DialogFragmentUnicastAddressListener) requireActivity()).getNextUnicastAddress(mElementCount); + unicastAddressInput.setText(MeshAddress.formatAddress(unicast, false)); + } catch (IllegalArgumentException ex) { + unicastAddressInputLayout.setError(ex.getMessage()); } }); @@ -137,13 +156,13 @@ private boolean validateInput(final String input) { try { - if(input.length() % 4 != 0 || !input.matches(Utils.HEX_PATTERN)) { + if (input.length() % 4 != 0 || !input.matches(Utils.HEX_PATTERN)) { unicastAddressInputLayout.setError(getString(R.string.invalid_address_value)); return false; } final int unicastAddress = Integer.parseInt(input, 16); - if(!MeshAddress.isValidUnicastAddress(unicastAddress)) { + if (!MeshAddress.isValidUnicastAddress(unicastAddress)) { unicastAddressInputLayout.setError("Unicast address must range from 0x0001 - 0x7FFFF"); return false; } @@ -156,7 +175,9 @@ private boolean validateInput(final String input) { public interface DialogFragmentUnicastAddressListener { - void setUnicastAddress(final int unicastAddress); + boolean setUnicastAddress(final int unicastAddress); + + int getNextUnicastAddress(final int elementCount); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/NetKeyListener.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/NetKeyListener.java new file mode 100644 index 000000000..019a54749 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/NetKeyListener.java @@ -0,0 +1,11 @@ +package no.nordicsemi.android.nrfmeshprovisioner.dialog; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.meshprovisioner.NetworkKey; + +public interface NetKeyListener { + + void onKeyUpdated(@NonNull final NetworkKey key); + + void onKeyNameUpdated(@NonNull final String nodeName); +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddAppKeyActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddAppKeyActivity.java new file mode 100644 index 000000000..671a5c23b --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddAppKeyActivity.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import com.google.android.material.snackbar.Snackbar; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.NetworkKey; +import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.keys.adapter.ManageBoundNetKeyAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs.DialogFragmentEditAppKey; +import no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs.DialogFragmentKeyName; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AddAppKeyViewModel; + +public class AddAppKeyActivity extends AppCompatActivity implements Injectable, + MeshKeyListener, + ManageBoundNetKeyAdapter.OnItemClickListener { + + private static final String APPLICATION_KEY = "APPLICATION_KEY"; + @Inject + ViewModelProvider.Factory mViewModelFactory; + + @BindView(R.id.container) + CoordinatorLayout container; + TextView nameView; + TextView keyView; + TextView keyIndexView; + + private AddAppKeyViewModel mViewModel; + private ApplicationKey appKey; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_key); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(AddAppKeyViewModel.class); + ButterKnife.bind(this); + + if (savedInstanceState == null) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + appKey = network.createAppKey(); + } + } else { + appKey = savedInstanceState.getParcelable(APPLICATION_KEY); + } + + //Bind ui + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_add_app_key); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp); + + final View containerKey = findViewById(R.id.container_key); + containerKey.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_vpn_key_black_alpha_24dp)); + ((TextView) containerKey.findViewById(R.id.title)).setText(R.string.title_app_key); + keyView = containerKey.findViewById(R.id.text); + keyView.setVisibility(View.VISIBLE); + + final View containerKeyName = findViewById(R.id.container_key_name); + containerKeyName.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_label_black_alpha_24dp)); + ((TextView) containerKeyName.findViewById(R.id.title)).setText(R.string.name); + nameView = containerKeyName.findViewById(R.id.text); + nameView.setVisibility(View.VISIBLE); + + final View containerKeyIndex = findViewById(R.id.container_key_index); + containerKeyIndex.setClickable(false); + containerKeyIndex.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_index)); + ((TextView) containerKeyIndex.findViewById(R.id.title)).setText(R.string.title_key_index); + keyIndexView = containerKeyIndex.findViewById(R.id.text); + keyIndexView.setVisibility(View.VISIBLE); + + findViewById(R.id.net_key_container).setVisibility(View.VISIBLE); + + final RecyclerView netKeysRecyclerView = findViewById(R.id.recycler_view_keys); + netKeysRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + netKeysRecyclerView.setItemAnimator(new DefaultItemAnimator()); + final ManageBoundNetKeyAdapter adapter = new ManageBoundNetKeyAdapter(this, mViewModel.getNetworkLiveData().getNetworkKeys(), appKey); + adapter.setOnItemClickListener(this); + netKeysRecyclerView.setAdapter(adapter); + + containerKey.setOnClickListener(v -> { + if (appKey != null) { + final DialogFragmentEditAppKey fragment = DialogFragmentEditAppKey.newInstance(appKey.getKeyIndex(), appKey); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerKeyName.setOnClickListener(v -> { + if (appKey != null) { + final DialogFragmentKeyName fragment = DialogFragmentKeyName.newInstance(appKey.getName()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + updateUi(); + } + + private void updateUi() { + if (appKey != null) { + keyView.setText(MeshParserUtils.bytesToHex(appKey.getKey(), false)); + nameView.setText(appKey.getName()); + keyIndexView.setText(String.valueOf(appKey.getKeyIndex())); + } + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.menu_save, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + case R.id.action_save: + if (save()) { + onBackPressed(); + } + return true; + } + return false; + } + + private boolean save() { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + return network.addAppKey(appKey); + } + return false; + } + + @Override + protected void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(APPLICATION_KEY, appKey); + } + + @Override + public boolean onKeyNameUpdated(@NonNull final String name) { + if (appKey != null) { + appKey.setName(name); + nameView.setText(name); + return true; + } + return false; + } + + @Override + public boolean onKeyUpdated(final int position, @NonNull final String key) { + if (appKey != null) { + this.appKey.setKey(MeshParserUtils.toByteArray(key)); + keyView.setText(key); + return true; + } + return false; + } + + @Override + public ApplicationKey updateBoundNetKeyIndex(final int position, @NonNull final NetworkKey networkKey) { + final ApplicationKey key = appKey; + key.setBoundNetKeyIndex(networkKey.getKeyIndex()); + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + try { + if (network.updateAppKey(key)) { + appKey = key; + return key; + } + } catch (IllegalArgumentException ex) { + mViewModel.displaySnackBar(this, container, ex.getMessage(), Snackbar.LENGTH_LONG); + } + } + return appKey; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddAppKeysActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddAppKeysActivity.java new file mode 100644 index 000000000..fd58454a8 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddAppKeysActivity.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import android.os.Bundle; +import android.view.View; + +import com.google.android.material.snackbar.Snackbar; + +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.NetworkKey; +import no.nordicsemi.android.meshprovisioner.NodeKey; +import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyAdd; +import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyDelete; +import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyGet; +import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; +import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.keys.adapter.AddedAppKeyAdapter; + +public class AddAppKeysActivity extends AddKeysActivity implements Injectable, + AddedAppKeyAdapter.OnItemClickListener { + private AddedAppKeyAdapter adapter; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_added_app_keys); + mEmptyView = findViewById(R.id.empty_app_keys); + adapter = new AddedAppKeyAdapter(this, + mViewModel.getNetworkLiveData().getMeshNetwork().getAppKeys(), mViewModel.getSelectedMeshNode()); + enableAdapterClickListener(true); + recyclerViewKeys.setAdapter(adapter); + setUpObserver(); + } + + @Override + public void onItemClick(@NonNull final ApplicationKey appKey) { + if (!checkConnectivity()) + return; + final MeshMessage meshMessage; + final String message; + final NetworkKey networkKey = mViewModel.getNetworkLiveData().getMeshNetwork().getNetKey(appKey.getBoundNetKeyIndex()); + if (!mViewModel.isAppKeyAdded(appKey.getKeyIndex())) { + message = getString(R.string.adding_app_key); + meshMessage = new ConfigAppKeyAdd(networkKey, appKey); + } else { + message = getString(R.string.deleting_app_key); + meshMessage = new ConfigAppKeyDelete(networkKey, appKey); + } + mViewModel.displaySnackBar(this, container, message, Snackbar.LENGTH_SHORT); + sendMessage(meshMessage); + } + + @Override + public void onRefresh() { + super.onRefresh(); + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + if (node != null) { + for (NodeKey key : node.getAddedNetKeys()) { + final NetworkKey networkKey = mViewModel.getNetworkLiveData().getMeshNetwork().getNetKey(key.getIndex()); + final ConfigAppKeyGet configAppKeyGet = new ConfigAppKeyGet(networkKey); + mViewModel.getMessageQueue().add(configAppKeyGet); + } + sendMessage(mViewModel.getMessageQueue().peek()); + } + } + + protected void setUpObserver() { + mViewModel.getNetworkLiveData().observe(this, networkLiveData -> { + if (networkLiveData != null) { + final List keys = networkLiveData.getAppKeys(); + if (keys != null) { + mEmptyView.setVisibility(keys.isEmpty() ? View.VISIBLE : View.GONE); + } + } + }); + } + + @Override + void enableAdapterClickListener(final boolean enable) { + adapter.setOnItemClickListener(enable ? this : null); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddKeysActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddKeysActivity.java new file mode 100644 index 000000000..c2c63f36d --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddKeysActivity.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import android.os.Bundle; +import android.os.Handler; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ProgressBar; + +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyList; +import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyStatus; +import no.nordicsemi.android.meshprovisioner.transport.ConfigNetKeyStatus; +import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; +import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentError; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentConfigStatus; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentTransactionStatus; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AddKeysViewModel; + +public abstract class AddKeysActivity extends AppCompatActivity implements Injectable, SwipeRefreshLayout.OnRefreshListener { + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + @BindView(R.id.container) + protected CoordinatorLayout container; + @BindView(R.id.recycler_view_keys) + protected RecyclerView recyclerViewKeys; + @BindView(R.id.fab_add) + protected ExtendedFloatingActionButton fab; + @BindView(R.id.configuration_progress_bar) + protected ProgressBar mProgressbar; + @BindView(R.id.swipe_refresh) + protected SwipeRefreshLayout mSwipe; + + protected Handler mHandler; + protected View mEmptyView; + + protected AddKeysViewModel mViewModel; + protected boolean mIsConnected; + + abstract void enableAdapterClickListener(final boolean enable); + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(AddKeysViewModel.class); + setContentView(R.layout.activity_add_keys); + ButterKnife.bind(this); + mHandler = new Handler(); + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + mSwipe.setOnRefreshListener(this); + recyclerViewKeys.setLayoutManager(new LinearLayoutManager(this)); + final DividerItemDecoration dividerItemDecoration = + new DividerItemDecoration(recyclerViewKeys.getContext(), DividerItemDecoration.VERTICAL); + recyclerViewKeys.addItemDecoration(dividerItemDecoration); + recyclerViewKeys.setItemAnimator(new DefaultItemAnimator()); + fab.hide(); + + mViewModel.getMeshMessage().observe(this, meshMessage -> { + if (meshMessage instanceof ConfigNetKeyStatus) { + final ConfigNetKeyStatus status = (ConfigNetKeyStatus) meshMessage; + if (status.isSuccessful()) { + mViewModel.displaySnackBar(this, container, getString(R.string.operation_success), Snackbar.LENGTH_SHORT); + } else { + showDialogFragment(getString(R.string.title_netkey_status), status.getStatusCodeName()); + } + } else if (meshMessage instanceof ConfigAppKeyStatus) { + final ConfigAppKeyStatus status = (ConfigAppKeyStatus) meshMessage; + if (status.isSuccessful()) { + mViewModel.displaySnackBar(this, container, getString(R.string.operation_success), Snackbar.LENGTH_SHORT); + } else { + showDialogFragment(getString(R.string.title_appkey_status), status.getStatusCodeName()); + } + } else if (meshMessage instanceof ConfigAppKeyList) { + final ConfigAppKeyList status = (ConfigAppKeyList) meshMessage; + if (!mViewModel.getMessageQueue().isEmpty()) + mViewModel.getMessageQueue().remove(); + if (status.isSuccessful()) { + handleStatuses(); + } else { + showDialogFragment(getString(R.string.title_appkey_status), status.getStatusCodeName()); + } + } + hideProgressBar(); + }); + + mViewModel.isConnectedToProxy().observe(this, isConnected -> { + if (isConnected != null) { + mIsConnected = isConnected; + hideProgressBar(); + } + invalidateOptionsMenu(); + }); + + final Boolean isConnectedToNetwork = mViewModel.isConnectedToProxy().getValue(); + if (isConnectedToNetwork != null) { + mIsConnected = isConnectedToNetwork; + } + invalidateOptionsMenu(); + + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + if (mIsConnected) { + getMenuInflater().inflate(R.menu.disconnect, menu); + } else { + getMenuInflater().inflate(R.menu.connect, menu); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + case R.id.action_connect: + mViewModel.navigateToScannerActivity(this, false, Utils.CONNECT_TO_NETWORK, false); + return true; + case R.id.action_disconnect: + mViewModel.disconnect(); + return true; + default: + return false; + } + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + if (isFinishing()) { + mHandler.removeCallbacksAndMessages(null); + } + } + + private void showDialogFragment(@NonNull final String title, @NonNull final String message) { + if (getSupportFragmentManager().findFragmentByTag(Utils.DIALOG_FRAGMENT_KEY_STATUS) == null) { + final DialogFragmentConfigStatus fragmentKeyStatus = DialogFragmentConfigStatus.newInstance(title, message); + fragmentKeyStatus.show(getSupportFragmentManager(), Utils.DIALOG_FRAGMENT_KEY_STATUS); + } + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + protected final boolean checkConnectivity() { + if (!mIsConnected) { + mViewModel.displayDisconnectedSnackBar(this, container); + return false; + } + return true; + } + + protected final void showProgressbar() { + mHandler.postDelayed(mOperationTimeout, Utils.MESSAGE_TIME_OUT); + disableClickableViews(); + mProgressbar.setVisibility(View.VISIBLE); + } + + protected final void hideProgressBar() { + mSwipe.setRefreshing(false); + enableClickableViews(); + mProgressbar.setVisibility(View.INVISIBLE); + mHandler.removeCallbacks(mOperationTimeout); + } + + private final Runnable mOperationTimeout = () -> { + hideProgressBar(); + mViewModel.getMessageQueue().clear(); + DialogFragmentTransactionStatus fragmentMessage = DialogFragmentTransactionStatus.newInstance(getString(R.string.title_transaction_failed), getString(R.string.operation_timed_out)); + fragmentMessage.show(getSupportFragmentManager(), null); + }; + + protected void enableClickableViews() { + enableAdapterClickListener(true); + recyclerViewKeys.setEnabled(true); + recyclerViewKeys.setClickable(true); + } + + protected void disableClickableViews() { + enableAdapterClickListener(false); + recyclerViewKeys.setEnabled(false); + recyclerViewKeys.setClickable(false); + } + + private void handleStatuses() { + final MeshMessage message = mViewModel.getMessageQueue().peek(); + if (message != null) { + sendMessage(message); + } else { + mViewModel.displaySnackBar(this, container, getString(R.string.operation_success), Snackbar.LENGTH_SHORT); + } + } + + protected void sendMessage(final MeshMessage meshMessage) { + try { + if (!checkConnectivity()) + return; + showProgressbar(); + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + if (node != null) { + mViewModel.getMeshManagerApi().createMeshPdu(node.getUnicastAddress(), meshMessage); + } + } catch (IllegalArgumentException ex) { + hideProgressBar(); + final DialogFragmentError message = DialogFragmentError. + newInstance(getString(R.string.title_error), ex.getMessage()); + message.show(getSupportFragmentManager(), null); + } + } + + @Override + public void onRefresh() { + if (!checkConnectivity()) { + mSwipe.setRefreshing(false); + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddNetKeyActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddNetKeyActivity.java new file mode 100644 index 000000000..5811908e9 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddNetKeyActivity.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.NetworkKey; +import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs.DialogFragmentEditNetKey; +import no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs.DialogFragmentKeyName; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AddNetKeyViewModel; + +public class AddNetKeyActivity extends AppCompatActivity implements Injectable, MeshKeyListener { + + private static final String APPLICATION_KEY = "APPLICATION_KEY"; + @Inject + ViewModelProvider.Factory mViewModelFactory; + private TextView nameView; + private TextView keyView; + private TextView keyIndexView; + + private AddNetKeyViewModel mViewModel; + private NetworkKey netKey; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_key); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(AddNetKeyViewModel.class); + + //Bind ui + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_add_net_key); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp); + + final View containerKey = findViewById(R.id.container_key); + containerKey.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_vpn_key_black_alpha_24dp)); + ((TextView) containerKey.findViewById(R.id.title)).setText(R.string.title_net_key); + keyView = containerKey.findViewById(R.id.text); + keyView.setVisibility(View.VISIBLE); + + final View containerKeyName = findViewById(R.id.container_key_name); + containerKeyName.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_label_black_alpha_24dp)); + ((TextView) containerKeyName.findViewById(R.id.title)).setText(R.string.name); + nameView = containerKeyName.findViewById(R.id.text); + nameView.setVisibility(View.VISIBLE); + + final View containerKeyIndex = findViewById(R.id.container_key_index); + containerKeyIndex.setClickable(false); + containerKeyIndex.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_index)); + ((TextView) containerKeyIndex.findViewById(R.id.title)).setText(R.string.title_key_index); + keyIndexView = containerKeyIndex.findViewById(R.id.text); + keyIndexView.setVisibility(View.VISIBLE); + + containerKey.setOnClickListener(v -> { + if (netKey != null) { + final DialogFragmentEditNetKey fragment = DialogFragmentEditNetKey.newInstance(netKey.getKeyIndex(), netKey); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerKeyName.setOnClickListener(v -> { + if (netKey != null) { + final DialogFragmentKeyName fragment = DialogFragmentKeyName.newInstance(netKey.getName()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + if (savedInstanceState == null) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + netKey = network.createNetworkKey(); + } + } else { + netKey = savedInstanceState.getParcelable(APPLICATION_KEY); + } + updateUi(); + } + + private void updateUi(){ + if (netKey != null) { + keyView.setText(MeshParserUtils.bytesToHex(netKey.getKey(), false)); + nameView.setText(netKey.getName()); + keyIndexView.setText(String.valueOf(netKey.getKeyIndex())); + } + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.menu_save, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + case R.id.action_save: + if (save()) { + onBackPressed(); + } + return true; + } + return false; + } + + private boolean save() { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + return network.addNetKey(netKey); + } + return false; + } + + @Override + protected void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(APPLICATION_KEY, netKey); + } + + @Override + public boolean onKeyNameUpdated(@NonNull final String name) { + if (netKey != null) { + netKey.setName(name); + nameView.setText(name); + return true; + } + return false; + } + + @Override + public boolean onKeyUpdated(final int position, @NonNull final String key) { + if(netKey != null) { + this.netKey.setKey(MeshParserUtils.toByteArray(key)); + keyView.setText(key); + return true; + } + return false; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddNetKeysActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddNetKeysActivity.java new file mode 100644 index 000000000..f5495e5bf --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AddNetKeysActivity.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import android.os.Bundle; + +import com.google.android.material.snackbar.Snackbar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import no.nordicsemi.android.meshprovisioner.NetworkKey; +import no.nordicsemi.android.meshprovisioner.transport.ConfigNetKeyAdd; +import no.nordicsemi.android.meshprovisioner.transport.ConfigNetKeyDelete; +import no.nordicsemi.android.meshprovisioner.transport.ConfigNetKeyGet; +import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.keys.adapter.AddedNetKeyAdapter; + +public class AddNetKeysActivity extends AddKeysActivity implements Injectable, + AddedNetKeyAdapter.OnItemClickListener { + private AddedNetKeyAdapter adapter; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_added_net_keys); + mEmptyView = findViewById(R.id.empty_net_keys); + adapter = new AddedNetKeyAdapter(this, + mViewModel.getNetworkLiveData().getMeshNetwork().getNetKeys(), mViewModel.getSelectedMeshNode()); + enableAdapterClickListener(true); + recyclerViewKeys.setAdapter(adapter); + } + + @Override + public void onItemClick(@NonNull final NetworkKey networkKey) { + if (!checkConnectivity()) + return; + final MeshMessage meshMessage; + final String message; + if (!mViewModel.isNetKeyAdded(networkKey.getKeyIndex())) { + meshMessage = new ConfigNetKeyAdd(networkKey); + message = getString(R.string.adding_net_key); + } else { + meshMessage = new ConfigNetKeyDelete(networkKey); + message = getString(R.string.deleting_net_key); + } + mViewModel.displaySnackBar(this, container, message, Snackbar.LENGTH_SHORT); + sendMessage(meshMessage); + } + + @Override + public void onRefresh() { + super.onRefresh(); + final ConfigNetKeyGet configNetKeyGet = new ConfigNetKeyGet(); + sendMessage(configNetKeyGet); + } + + @Override + void enableAdapterClickListener(final boolean enable) { + adapter.setOnItemClickListener(enable ? this : null); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AppKeysActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AppKeysActivity.java new file mode 100644 index 000000000..f66c789b6 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/AppKeysActivity.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import java.util.List; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.NodeKey; +import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.keys.adapter.ManageAppKeyAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AppKeysViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; + +public class AppKeysActivity extends AppCompatActivity implements Injectable, + ManageAppKeyAdapter.OnItemClickListener, + ItemTouchHelperAdapter { + + public static final String RESULT_APP_KEY = "RESULT_KEY"; + public static final String RESULT_APP_KEY_INDEX = "RESULT_APP_KEY_INDEX"; + public static final String RESULT_APP_KEY_LIST_SIZE = "RESULT_APP_KEY_LIST_SIZE"; + public static final String EDIT_APP_KEY = "EDIT_APP_KEY"; + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + //UI Bindings + @BindView(R.id.empty_app_keys) + View mEmptyView; + @BindView(R.id.container) + CoordinatorLayout container; + + private AppKeysViewModel mViewModel; + private ManageAppKeyAdapter mAdapter; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_app_keys); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(AppKeysViewModel.class); + + //Bind ui + ButterKnife.bind(this); + + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + final ExtendedFloatingActionButton fab = findViewById(R.id.fab_add); + final RecyclerView appKeysRecyclerView = findViewById(R.id.recycler_view_keys); + appKeysRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + final DividerItemDecoration dividerItemDecoration = + new DividerItemDecoration(appKeysRecyclerView.getContext(), DividerItemDecoration.VERTICAL); + appKeysRecyclerView.addItemDecoration(dividerItemDecoration); + appKeysRecyclerView.setItemAnimator(new DefaultItemAnimator()); + + final Bundle bundle = getIntent().getExtras(); + if (bundle != null) { + switch (bundle.getInt(Utils.EXTRA_DATA)) { + case Utils.MANAGE_APP_KEY: + break; + case Utils.ADD_APP_KEY: + getSupportActionBar().setTitle(R.string.title_select_app_key); + fab.hide(); + mAdapter = new ManageAppKeyAdapter(this, mViewModel.getNetworkLiveData()); + mAdapter.setOnItemClickListener(this); + appKeysRecyclerView.setAdapter(mAdapter); + setUpObserver(); + break; + case Utils.BIND_APP_KEY: + case Utils.PUBLICATION_APP_KEY: + getSupportActionBar().setTitle(R.string.title_select_app_key); + fab.hide(); + //Get selected mesh node + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + if (node != null) { + final List applicationKeys = node.getAddedAppKeys(); + if (!applicationKeys.isEmpty()) { + mAdapter = new ManageAppKeyAdapter(this, mViewModel.getNetworkLiveData().getAppKeys(), applicationKeys); + mAdapter.setOnItemClickListener(this); + appKeysRecyclerView.setAdapter(mAdapter); + } else { + final TextView textView = mEmptyView.findViewById(R.id.rationale); + textView.setText(R.string.no_added_app_keys_rationale); + mEmptyView.setVisibility(View.VISIBLE); + } + } + break; + } + } else { + getSupportActionBar().setTitle(R.string.title_manage_app_keys); + final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); + final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); + itemTouchHelper.attachToRecyclerView(appKeysRecyclerView); + mAdapter = new ManageAppKeyAdapter(this, mViewModel.getNetworkLiveData()); + mAdapter.setOnItemClickListener(this); + appKeysRecyclerView.setAdapter(mAdapter); + setUpObserver(); + } + + + fab.setOnClickListener(v -> { + final Intent intent = new Intent(this, AddAppKeyActivity.class); + startActivity(intent); + }); + + appKeysRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { + super.onScrolled(recyclerView, dx, dy); + final LinearLayoutManager m = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (m != null) { + if (m.findFirstCompletelyVisibleItemPosition() == 0) { + fab.extend(true); + } else { + fab.shrink(true); + } + } + } + }); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return false; + } + + @Override + public void onBackPressed() { + final Bundle bundle = getIntent().getExtras(); + if (bundle != null) { + if (bundle.getInt(Utils.EXTRA_DATA) == Utils.MANAGE_APP_KEY) { + Intent returnIntent = new Intent(); + returnIntent.putExtra(RESULT_APP_KEY_LIST_SIZE, mAdapter.getItemCount()); + setResult(Activity.RESULT_OK, returnIntent); + finish(); + } + } + super.onBackPressed(); + } + + @Override + public void onItemClick(final int position, @NonNull final ApplicationKey appKey) { + final Bundle bundle = getIntent().getExtras(); + if (bundle != null) { + switch (bundle.getInt(Utils.EXTRA_DATA)) { + case Utils.ADD_APP_KEY: + case Utils.BIND_APP_KEY: + case Utils.PUBLICATION_APP_KEY: + Intent returnIntent = new Intent(); + returnIntent.putExtra(RESULT_APP_KEY_INDEX, position); + returnIntent.putExtra(RESULT_APP_KEY, appKey); + setResult(Activity.RESULT_OK, returnIntent); + finish(); + + } + } else { + final Intent intent = new Intent(this, EditAppKeyActivity.class); + intent.putExtra(EDIT_APP_KEY, appKey.getKeyIndex()); + startActivity(intent); + } + } + + @Override + public void onItemDismiss(final RemovableViewHolder viewHolder) { + final ApplicationKey key = (ApplicationKey) viewHolder.getSwipeableView().getTag(); + try { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network.removeAppKey(key)) { + displaySnackBar(key); + // Show the empty view + final boolean empty = mAdapter.getItemCount() == 0; + if (empty) { + mEmptyView.setVisibility(View.VISIBLE); + } + } + } catch (Exception ex) { + mAdapter.notifyDataSetChanged(); + mViewModel.displaySnackBar(this, container, ex.getMessage(), Snackbar.LENGTH_LONG); + } + } + + @Override + public void onItemDismissFailed(final RemovableViewHolder viewHolder) { + //Do nothing + } + + private void setUpObserver() { + mViewModel.getNetworkLiveData().observe(this, networkLiveData -> { + if (networkLiveData != null) { + final List keys = networkLiveData.getAppKeys(); + if (keys != null) { + mEmptyView.setVisibility(keys.isEmpty() ? View.VISIBLE : View.GONE); + } + } + }); + } + + private void displaySnackBar(@NonNull final ApplicationKey appKey) { + Snackbar.make(container, getString(R.string.app_key_deleted), Snackbar.LENGTH_LONG) + .setAction(getString(R.string.undo), view -> { + mEmptyView.setVisibility(View.INVISIBLE); + mViewModel.getNetworkLiveData().getMeshNetwork().addAppKey(appKey); + }) + .setActionTextColor(getResources().getColor(R.color.colorPrimaryDark)) + .show(); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/EditAppKeyActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/EditAppKeyActivity.java new file mode 100644 index 000000000..3e728bb30 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/EditAppKeyActivity.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import com.google.android.material.snackbar.Snackbar; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.NetworkKey; +import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.keys.adapter.ManageBoundNetKeyAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs.DialogFragmentEditAppKey; +import no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs.DialogFragmentKeyName; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.EditAppKeyViewModel; + +public class EditAppKeyActivity extends AppCompatActivity implements Injectable, + MeshKeyListener, + ManageBoundNetKeyAdapter.OnItemClickListener { + + @Inject + ViewModelProvider.Factory mViewModelFactory; + @BindView(R.id.container) + View container; + + private EditAppKeyViewModel mViewModel; + private ApplicationKey appKey; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_key); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(EditAppKeyViewModel.class); + ButterKnife.bind(this); + //noinspection ConstantConditions + final int index = getIntent().getExtras().getInt(AppKeysActivity.EDIT_APP_KEY); + appKey = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(index); + + //Bind ui + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_edit_app_key); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + final View containerKey = findViewById(R.id.container_key); + containerKey.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_lock_open_black_alpha_24dp)); + ((TextView) containerKey.findViewById(R.id.title)).setText(R.string.title_app_key); + final TextView keyView = containerKey.findViewById(R.id.text); + keyView.setVisibility(View.VISIBLE); + + final View containerKeyName = findViewById(R.id.container_key_name); + containerKeyName.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_label_black_alpha_24dp)); + ((TextView) containerKeyName.findViewById(R.id.title)).setText(R.string.name); + final TextView name = containerKeyName.findViewById(R.id.text); + name.setVisibility(View.VISIBLE); + + final View containerKeyIndex = findViewById(R.id.container_key_index); + containerKeyIndex.setClickable(false); + containerKeyIndex.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_index)); + ((TextView) containerKeyIndex.findViewById(R.id.title)).setText(R.string.title_key_index); + final TextView keyIndexView = containerKeyIndex.findViewById(R.id.text); + keyIndexView.setVisibility(View.VISIBLE); + + findViewById(R.id.net_key_container).setVisibility(View.VISIBLE); + final RecyclerView netKeysRecyclerView = findViewById(R.id.recycler_view_keys); + netKeysRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + netKeysRecyclerView.setItemAnimator(new DefaultItemAnimator()); + final ManageBoundNetKeyAdapter mAdapter = new ManageBoundNetKeyAdapter(this, mViewModel.getNetworkLiveData().getNetworkKeys(), appKey); + mAdapter.setOnItemClickListener(this); + netKeysRecyclerView.setAdapter(mAdapter); + + containerKey.setOnClickListener(v -> { + if (appKey != null) { + final DialogFragmentEditAppKey fragment = DialogFragmentEditAppKey.newInstance(appKey.getKeyIndex(), appKey); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerKeyName.setOnClickListener(v -> { + if (appKey != null) { + final DialogFragmentKeyName fragment = DialogFragmentKeyName.newInstance(appKey.getName()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + mViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { + if (appKey != null) { + this.appKey = meshNetworkLiveData.getMeshNetwork().getAppKey(appKey.getKeyIndex()); + keyView.setText(MeshParserUtils.bytesToHex(appKey.getKey(), false)); + name.setText(appKey.getName()); + keyIndexView.setText(String.valueOf(appKey.getKeyIndex())); + } + }); + + if (savedInstanceState == null) { + keyView.setText(MeshParserUtils.bytesToHex(appKey.getKey(), false)); + name.setText(appKey.getName()); + } + keyIndexView.setText(String.valueOf(appKey.getKeyIndex())); + + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return false; + } + + @Override + public boolean onKeyNameUpdated(@NonNull final String name) { + if (appKey != null) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + appKey.setName(name); + return network.updateAppKey(appKey); + } + } + return false; + } + + @Override + public boolean onKeyUpdated(final int position, @NonNull final String key) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + return network.updateAppKey(appKey, key); + } + return false; + } + + @Override + public ApplicationKey updateBoundNetKeyIndex(final int position, @NonNull final NetworkKey networkKey) { + try { + final ApplicationKey key = appKey.clone(); + key.setBoundNetKeyIndex(networkKey.getKeyIndex()); + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + try { + if (network.updateAppKey(key)) { + appKey = key; + return key; + } + } catch (IllegalArgumentException ex) { + displaySnackBar(ex.getMessage()); + } + } + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return appKey; + } + + private void displaySnackBar(final String message) { + Snackbar.make(container, message, Snackbar.LENGTH_LONG) + .show(); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/EditNetKeyActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/EditNetKeyActivity.java new file mode 100644 index 000000000..b80091325 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/EditNetKeyActivity.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.NetworkKey; +import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs.DialogFragmentEditNetKey; +import no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs.DialogFragmentKeyName; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.EditNetKeyViewModel; + +public class EditNetKeyActivity extends AppCompatActivity implements Injectable, MeshKeyListener { + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + private EditNetKeyViewModel mViewModel; + private NetworkKey networkKey; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_key); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(EditNetKeyViewModel.class); + + //noinspection ConstantConditions + final int index = getIntent().getExtras().getInt(NetKeysActivity.EDIT_NET_KEY); + networkKey = mViewModel.getNetworkLiveData().getMeshNetwork().getNetKey(index); + + //Bind ui + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_edit_net_key); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + final View containerKey = findViewById(R.id.container_key); + containerKey.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_lock_open_black_alpha_24dp)); + ((TextView) containerKey.findViewById(R.id.title)).setText(R.string.title_net_key); + final TextView keyView = containerKey.findViewById(R.id.text); + keyView.setVisibility(View.VISIBLE); + + final View containerKeyName = findViewById(R.id.container_key_name); + containerKeyName.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_label_black_alpha_24dp)); + ((TextView) containerKeyName.findViewById(R.id.title)).setText(R.string.name); + final TextView name = containerKeyName.findViewById(R.id.text); + name.setVisibility(View.VISIBLE); + + final View containerKeyIndex = findViewById(R.id.container_key_index); + containerKeyIndex.setClickable(false); + containerKeyIndex.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_index)); + ((TextView) containerKeyIndex.findViewById(R.id.title)).setText(R.string.title_key_index); + final TextView keyIndexView = containerKeyIndex.findViewById(R.id.text); + keyIndexView.setVisibility(View.VISIBLE); + + containerKey.setOnClickListener(v -> { + if (networkKey != null) { + final DialogFragmentEditNetKey fragment = DialogFragmentEditNetKey.newInstance(networkKey.getKeyIndex(), networkKey); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerKeyName.setOnClickListener(v -> { + if (networkKey != null) { + final DialogFragmentKeyName fragment = DialogFragmentKeyName.newInstance(networkKey.getName()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + mViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { + if (networkKey != null) { + this.networkKey = meshNetworkLiveData.getMeshNetwork().getNetKey(networkKey.getKeyIndex()); + keyView.setText(MeshParserUtils.bytesToHex(networkKey.getKey(), false)); + name.setText(networkKey.getName()); + keyIndexView.setText(String.valueOf(networkKey.getKeyIndex())); + } + }); + + if (savedInstanceState == null) { + keyView.setText(MeshParserUtils.bytesToHex(networkKey.getKey(), false)); + name.setText(networkKey.getName()); + } + keyIndexView.setText(String.valueOf(networkKey.getKeyIndex())); + + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return false; + } + + @Override + public boolean onKeyNameUpdated(@NonNull final String name) { + if (networkKey != null) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + return network.updateNetKey(networkKey, name); + } + } + return false; + } + + @Override + public boolean onKeyUpdated(final int position, @NonNull final String key) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + return network.updateNetKey(networkKey, key); + } + return false; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/MeshKeyListener.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/MeshKeyListener.java new file mode 100644 index 000000000..0011e5259 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/MeshKeyListener.java @@ -0,0 +1,23 @@ +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import androidx.annotation.NonNull; + +public interface MeshKeyListener { + + /** + * Invoked when the name of the key has been changed + * + * @param name Name + * @return true if the name was set or false otherwise + */ + boolean onKeyNameUpdated(@NonNull final String name); + + /** + * Invoked when the name of the key has been changed + * + * @param position position of the key in the list + * @param key Updated key + * @return true if the key was updated or false otherwise + */ + boolean onKeyUpdated(final int position, @NonNull final String key); +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/NetKeysActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/NetKeysActivity.java new file mode 100644 index 000000000..58b164b1e --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/NetKeysActivity.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys; + +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.cardview.widget.CardView; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.NetworkKey; +import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.keys.adapter.ManageNetKeyAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NetKeysViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; + +public class NetKeysActivity extends AppCompatActivity implements Injectable, + ManageNetKeyAdapter.OnItemClickListener, + ItemTouchHelperAdapter { + + public static final String EDIT_NET_KEY = "EDIT_NET_KEY"; + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + //UI Bindings + @BindView(R.id.container) + CoordinatorLayout container; + @BindView(R.id.scroll_container) + ScrollView scrollView; + @BindView(R.id.sub_net_key_card) + CardView mSubNetKeyCard; + @BindView(R.id.recycler_view_keys) + RecyclerView netKeysRecyclerView; + @BindView(R.id.fab_add) + ExtendedFloatingActionButton fab; + @BindView(R.id.container_primary_net_key) + View containerKey; + @BindView(R.id.div3) + View divider; + @BindView(R.id.delete_hint) + View deleteHint; + + private NetKeysViewModel mViewModel; + private ManageNetKeyAdapter mAdapter; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_net_keys); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(NetKeysViewModel.class); + + //Bind ui + ButterKnife.bind(this); + + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_manage_net_keys); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + containerKey.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_vpn_key_black_alpha_24dp)); + final TextView keyTitle = containerKey.findViewById(R.id.title); + final TextView keyView = containerKey.findViewById(R.id.text); + keyView.setVisibility(View.VISIBLE); + + netKeysRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + netKeysRecyclerView.setItemAnimator(new DefaultItemAnimator()); + + final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); + final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); + mAdapter = new ManageNetKeyAdapter(this, mViewModel.getNetworkLiveData()); + mAdapter.setOnItemClickListener(this); + netKeysRecyclerView.setAdapter(mAdapter); + + mViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { + final MeshNetwork network = meshNetworkLiveData.getMeshNetwork(); + if (network != null) { + final NetworkKey networkKey = network.getPrimaryNetworkKey(); + if (networkKey != null) { + keyTitle.setText(networkKey.getName()); + keyView.setText(MeshParserUtils.bytesToHex(networkKey.getKey(), false)); + + if (network.getNetKeys().size() > 1) { + mSubNetKeyCard.setVisibility(View.VISIBLE); + } else { + mSubNetKeyCard.setVisibility(View.GONE); + } + } + } + }); + + final Bundle bundle = getIntent().getExtras(); + if (bundle != null) { + switch (bundle.getInt(Utils.EXTRA_DATA)) { + case Utils.MANAGE_NET_KEY: + setUpClickListeners(); + itemTouchHelper.attachToRecyclerView(netKeysRecyclerView); + break; + case Utils.ADD_NET_KEY: + getSupportActionBar().setTitle(R.string.title_select_net_key); + divider.setVisibility(View.GONE); + deleteHint.setVisibility(View.GONE); + fab.hide(); + mAdapter = new ManageNetKeyAdapter(this, mViewModel.getNetworkLiveData()); + mAdapter.setOnItemClickListener(this); + netKeysRecyclerView.setAdapter(mAdapter); + break; + } + + } + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return false; + } + + @Override + public void onItemClick(final int position, @NonNull final NetworkKey networkKey) { + final Intent intent = new Intent(this, EditNetKeyActivity.class); + intent.putExtra(EDIT_NET_KEY, networkKey.getKeyIndex()); + startActivity(intent); + } + + @Override + public void onItemDismiss(final RemovableViewHolder viewHolder) { + final NetworkKey key = (NetworkKey) viewHolder.getSwipeableView().getTag(); + try { + if (removeNetKey(key)) { + displaySnackBar(key); + } + } catch (Exception ex) { + mAdapter.notifyDataSetChanged(); + mViewModel.displaySnackBar(this, container, ex.getMessage(), Snackbar.LENGTH_LONG); + } + } + + @Override + public void onItemDismissFailed(final RemovableViewHolder viewHolder) { + + } + + private void displaySnackBar(@NonNull final NetworkKey networkKey) { + Snackbar.make(container, getString(R.string.net_key_deleted), Snackbar.LENGTH_LONG) + .setAction(getString(R.string.undo), view -> addNetKey(networkKey)) + .setActionTextColor(getResources().getColor(R.color.colorPrimaryDark)) + .show(); + } + + @SuppressWarnings("UnusedReturnValue") + private boolean addNetKey(@NonNull final NetworkKey networkKey) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + return network.addNetKey(networkKey); + } + return false; + } + + private boolean removeNetKey(@NonNull final NetworkKey networkKey) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + return network.removeNetKey(networkKey); + } + return false; + } + + private void setUpClickListeners() { + containerKey.setOnClickListener(v -> { + final Intent intent = new Intent(this, EditNetKeyActivity.class); + intent.putExtra(EDIT_NET_KEY, 0); + startActivity(intent); + }); + + fab.setOnClickListener(v -> { + final Intent intent = new Intent(this, AddNetKeyActivity.class); + startActivity(intent); + }); + + scrollView.getViewTreeObserver().addOnScrollChangedListener(() -> { + if (scrollView.getScrollY() == 0) { + fab.extend(true); + } else { + fab.shrink(true); + } + }); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AddedAppKeyAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/AddedAppKeyAdapter.java similarity index 64% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AddedAppKeyAdapter.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/AddedAppKeyAdapter.java index 8d570bde4..c16e04025 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AddedAppKeyAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/AddedAppKeyAdapter.java @@ -20,27 +20,29 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.adapter; +package no.nordicsemi.android.nrfmeshprovisioner.keys.adapter; -import android.arch.lifecycle.LiveData; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.CheckBox; import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.NodeKey; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.nrfmeshprovisioner.NodeConfigurationActivity; import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; @@ -48,18 +50,28 @@ public class AddedAppKeyAdapter extends RecyclerView.Adapter { private final List appKeys = new ArrayList<>(); + private final List addedAppKeys = new ArrayList<>(); private final Context mContext; private OnItemClickListener mOnItemClickListener; - public AddedAppKeyAdapter(final NodeConfigurationActivity activity, final LiveData meshNodeLiveData) { - this.mContext = activity.getApplicationContext(); - meshNodeLiveData.observe(activity, meshNode -> { - if (meshNode != null) { - appKeys.clear(); - appKeys.addAll(meshNode.getAddedApplicationKeys().values()); - Collections.sort(appKeys, Utils.appKeyComparator); - notifyDataSetChanged(); + public AddedAppKeyAdapter(@NonNull final Context context, + @NonNull final List appKeys, + @NonNull final LiveData meshNodeLiveData) { + this.mContext = context; + this.appKeys.clear(); + this.appKeys.addAll(appKeys); + Collections.sort(this.appKeys, Utils.appKeyComparator); + meshNodeLiveData.observe((LifecycleOwner) context, meshNode -> { + addedAppKeys.clear(); + for (NodeKey nodeKey : meshNode.getAddedAppKeys()) { + for (ApplicationKey applicationKey : appKeys) { + if (nodeKey.getIndex() == applicationKey.getKeyIndex()) { + addedAppKeys.add(applicationKey); + } + } } + Collections.sort(addedAppKeys, Utils.appKeyComparator); + notifyDataSetChanged(); }); } @@ -70,17 +82,20 @@ public void setOnItemClickListener(final AddedAppKeyAdapter.OnItemClickListener @NonNull @Override public AddedAppKeyAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.app_key_item, parent, false); + final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.row_item_key, parent, false); return new AddedAppKeyAdapter.ViewHolder(layoutView); } @Override public void onBindViewHolder(@NonNull final AddedAppKeyAdapter.ViewHolder holder, final int position) { - if (appKeys.size() > 0) { - final ApplicationKey key = appKeys.get(position); - holder.appKeyId.setText(mContext.getString(R.string.app_key_item, key.getKeyIndex())); - final String appKey = MeshParserUtils.bytesToHex(key.getKey(), false); - holder.appKey.setText(appKey.toUpperCase()); + final ApplicationKey key = appKeys.get(position); + holder.keyName.setText(key.getName()); + final String appKey = MeshParserUtils.bytesToHex(key.getKey(), false); + holder.key.setText(appKey.toUpperCase()); + if (addedAppKeys.contains(key)) { + holder.check.setChecked(true); + } else { + holder.check.setChecked(false); } } @@ -100,20 +115,22 @@ public boolean isEmpty() { @FunctionalInterface public interface OnItemClickListener { - void onItemClick(final ApplicationKey appKey); + void onItemClick(@NonNull final ApplicationKey appKey); } final class ViewHolder extends RemovableViewHolder { - @BindView(R.id.app_key_id) - TextView appKeyId; - @BindView(R.id.app_key) - TextView appKey; + @BindView(R.id.title) + TextView keyName; + @BindView(R.id.subtitle) + TextView key; + @BindView(R.id.check) + CheckBox check; private ViewHolder(final View view) { super(view); ButterKnife.bind(this, view); - view.findViewById(R.id.removable).setOnClickListener(v -> { + check.setOnClickListener(v -> { if (mOnItemClickListener != null) { final ApplicationKey key = appKeys.get(getAdapterPosition()); mOnItemClickListener.onItemClick(key); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/AddedNetKeyAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/AddedNetKeyAdapter.java new file mode 100644 index 000000000..d47abb50b --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/AddedNetKeyAdapter.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.keys.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.NetworkKey; +import no.nordicsemi.android.meshprovisioner.NodeKey; +import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; + +public class AddedNetKeyAdapter extends RecyclerView.Adapter { + + private final List netKeys = new ArrayList<>(); + private final List addedNetKeys = new ArrayList<>(); + private final Context mContext; + private OnItemClickListener mOnItemClickListener; + + public AddedNetKeyAdapter(@NonNull final Context context, + @NonNull final List netKeys, + @NonNull final LiveData meshNodeLiveData) { + this.mContext = context; + this.netKeys.addAll(netKeys); + Collections.sort(this.netKeys, Utils.netKeyComparator); + meshNodeLiveData.observe((LifecycleOwner) context, meshNode -> { + addedNetKeys.clear(); + for (NodeKey nodeKey : meshNode.getAddedNetKeys()) { + for (NetworkKey networkKey : netKeys) { + if (nodeKey.getIndex() == networkKey.getKeyIndex()) { + addedNetKeys.add(networkKey); + } + } + } + Collections.sort(addedNetKeys, Utils.netKeyComparator); + notifyDataSetChanged(); + }); + } + + public void setOnItemClickListener(final AddedNetKeyAdapter.OnItemClickListener listener) { + mOnItemClickListener = listener; + } + + @NonNull + @Override + public AddedNetKeyAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.row_item_key, parent, false); + return new AddedNetKeyAdapter.ViewHolder(layoutView); + } + + @Override + public void onBindViewHolder(@NonNull final AddedNetKeyAdapter.ViewHolder holder, final int position) { + final NetworkKey key = netKeys.get(position); + holder.keyName.setText(key.getName()); + final String appKey = MeshParserUtils.bytesToHex(key.getKey(), false); + holder.key.setText(appKey.toUpperCase()); + if (addedNetKeys.contains(key)) { + holder.check.setChecked(true); + } else { + holder.check.setChecked(false); + } + } + + @Override + public long getItemId(final int position) { + return position; + } + + @Override + public int getItemCount() { + return netKeys.size(); + } + + public boolean isEmpty() { + return getItemCount() == 0; + } + + @FunctionalInterface + public interface OnItemClickListener { + void onItemClick(final NetworkKey appKey); + } + + final class ViewHolder extends RemovableViewHolder { + + @BindView(R.id.title) + TextView keyName; + @BindView(R.id.subtitle) + TextView key; + @BindView(R.id.check) + CheckBox check; + + private ViewHolder(final View view) { + super(view); + ButterKnife.bind(this, view); + check.setOnClickListener(v -> { + if (mOnItemClickListener != null) { + check.setChecked(!check.isChecked()); + final NetworkKey key = netKeys.get(getAdapterPosition()); + mOnItemClickListener.onItemClick(key); + } + }); + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/BoundAppKeysAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/BoundAppKeysAdapter.java similarity index 58% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/BoundAppKeysAdapter.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/BoundAppKeysAdapter.java index 22f3f680b..42840ffb7 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/BoundAppKeysAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/BoundAppKeysAdapter.java @@ -20,82 +20,71 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.adapter; +package no.nordicsemi.android.nrfmeshprovisioner.keys.adapter; -import android.arch.lifecycle.LiveData; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.Collections; +import java.util.List; +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; import no.nordicsemi.android.meshprovisioner.transport.MeshModel; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.nrfmeshprovisioner.BaseModelConfigurationActivity; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; public class BoundAppKeysAdapter extends RecyclerView.Adapter { - private final ArrayList mAppKeys = new ArrayList<>(); + private final ArrayList appKeys = new ArrayList<>(); private final Context mContext; - private Map mBoundAppKeys = new LinkedHashMap<>(); - private OnItemClickListener mOnItemClickListener; - - public BoundAppKeysAdapter(final BaseModelConfigurationActivity activity, final LiveData meshModelLiveData) { - this.mContext = activity; - meshModelLiveData.observe(activity, meshModel -> { - if(meshModel != null) { - if (meshModel.getBoundApplicationKeys() != null) { - mBoundAppKeys.putAll(meshModel.getBoundApplicationKeys()); - mAppKeys.clear(); - mAppKeys.addAll(meshModel.getBoundApplicationKeys().values()); - notifyDataSetChanged(); + + public BoundAppKeysAdapter(@NonNull final Context context, + @NonNull final List appKeys, + @NonNull final LiveData meshModelLiveData) { + this.mContext = context; + meshModelLiveData.observe((LifecycleOwner) context, meshModel -> { + if (meshModel != null) { + this.appKeys.clear(); + for (Integer index : meshModel.getBoundAppKeyIndexes()) { + for (ApplicationKey applicationKey : appKeys) { + if (index == applicationKey.getKeyIndex()) { + this.appKeys.add(applicationKey); + } + } } + Collections.sort(this.appKeys, Utils.appKeyComparator); + notifyDataSetChanged(); } }); } - public void setOnItemClickListener(final BoundAppKeysAdapter.OnItemClickListener listener) { - mOnItemClickListener = listener; - } - @NonNull @Override public BoundAppKeysAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.bound_app_key_item, parent, false); + final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.removable_row_item, parent, false); return new BoundAppKeysAdapter.ViewHolder(layoutView); } @Override public void onBindViewHolder(@NonNull final BoundAppKeysAdapter.ViewHolder holder, final int position) { - if(mAppKeys.size() > 0) { - final ApplicationKey applicationKey = mAppKeys.get(position); + if (appKeys.size() > 0) { + final ApplicationKey applicationKey = appKeys.get(position); final String appKey = MeshParserUtils.bytesToHex(applicationKey.getKey(), false); - final Integer appKeyIndex = applicationKey.getKeyIndex(); - if(appKeyIndex != null) { - holder.appKeyId.setText(mContext.getString(R.string.app_key_index_item, appKeyIndex)); - holder.appKey.setText(appKey.toUpperCase()); - } - } - } - - private Integer getAppKeyIndex(final String appKey){ - for(Integer key : mBoundAppKeys.keySet()){ - if(mBoundAppKeys.get(key).equals(appKey)){ - return key; - } + holder.appKeyName.setText(applicationKey.getName()); + holder.appKey.setText(appKey.toUpperCase()); } - return null; } @Override @@ -105,7 +94,7 @@ public long getItemId(final int position) { @Override public int getItemCount() { - return mAppKeys.size(); + return appKeys.size(); } public boolean isEmpty() { @@ -113,8 +102,8 @@ public boolean isEmpty() { } public ApplicationKey getAppKey(final int position) { - if(!mAppKeys.isEmpty()){ - return mAppKeys.get(position); + if (!appKeys.isEmpty()) { + return appKeys.get(position); } return null; } @@ -126,19 +115,14 @@ public interface OnItemClickListener { public final class ViewHolder extends RemovableViewHolder { - @BindView(R.id.app_key_id) - TextView appKeyId; - @BindView(R.id.app_key) + @BindView(R.id.title) + TextView appKeyName; + @BindView(R.id.subtitle) TextView appKey; private ViewHolder(final View view) { super(view); ButterKnife.bind(this, view); - view.findViewById(R.id.removable).setOnClickListener(v -> { - if (mOnItemClickListener != null) { - mOnItemClickListener.onItemClick(getAdapterPosition(), mAppKeys.get(getAdapterPosition())); - } - }); } } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ManageAppKeyAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/ManageAppKeyAdapter.java similarity index 76% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ManageAppKeyAdapter.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/ManageAppKeyAdapter.java index 92dbf70ad..6f6022fc9 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ManageAppKeyAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/ManageAppKeyAdapter.java @@ -20,11 +20,9 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.adapter; +package no.nordicsemi.android.nrfmeshprovisioner.keys.adapter; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -34,11 +32,14 @@ import java.util.Collections; import java.util.List; +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.NodeKey; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.nrfmeshprovisioner.ManageAppKeysActivity; import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.MeshNetworkLiveData; @@ -50,10 +51,9 @@ public class ManageAppKeyAdapter extends RecyclerView.Adapter { - //noinspection ConstantConditions + public ManageAppKeyAdapter(@NonNull final Context context, @NonNull final MeshNetworkLiveData meshNetworkLiveData) { + this.mContext = context; + meshNetworkLiveData.observe((LifecycleOwner) context, networkData -> { final List keys = networkData.getAppKeys(); if (keys != null) { appKeys.clear(); @@ -64,10 +64,18 @@ public ManageAppKeyAdapter(final ManageAppKeysActivity activity, final MeshNetwo }); } - public ManageAppKeyAdapter(final ManageAppKeysActivity activity, final List appKeys) { - this.mContext = activity; - this.appKeys.addAll(appKeys); - Collections.sort(appKeys, Utils.appKeyComparator); + public ManageAppKeyAdapter(@NonNull final Context context, + @NonNull final List appKeys, + @NonNull final List appKeyIndexes) { + this.mContext = context; + for (NodeKey nodeKey : appKeyIndexes) { + for (ApplicationKey applicationKey : appKeys) { + if (nodeKey.getIndex() == applicationKey.getKeyIndex()) { + this.appKeys.add(applicationKey); + } + } + } + Collections.sort(this.appKeys, Utils.appKeyComparator); notifyDataSetChanged(); } @@ -78,7 +86,7 @@ public void setOnItemClickListener(final ManageAppKeyAdapter.OnItemClickListener @NonNull @Override public ManageAppKeyAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.app_key_item, parent, false); + final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.removable_row_item, parent, false); return new ManageAppKeyAdapter.ViewHolder(layoutView); } @@ -86,11 +94,10 @@ public ManageAppKeyAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGrou public void onBindViewHolder(@NonNull final ManageAppKeyAdapter.ViewHolder holder, final int position) { if (appKeys.size() > 0) { final ApplicationKey appKey = appKeys.get(position); - holder.appKeyId.setText(mContext.getString(R.string.app_key_item, appKey.getKeyIndex())); + holder.appKeyName.setText(appKey.getName()); final String key = MeshParserUtils.bytesToHex(appKey.getKey(), false); holder.appKey.setText(key.toUpperCase()); holder.getSwipeableView().setTag(appKey); - } } @@ -110,14 +117,14 @@ public boolean isEmpty() { @FunctionalInterface public interface OnItemClickListener { - void onItemClick(final int position, final ApplicationKey appKey); + void onItemClick(final int position, @NonNull final ApplicationKey appKey); } final class ViewHolder extends RemovableViewHolder { - @BindView(R.id.app_key_id) - TextView appKeyId; - @BindView(R.id.app_key) + @BindView(R.id.title) + TextView appKeyName; + @BindView(R.id.subtitle) TextView appKey; private ViewHolder(final View view) { diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AppKeyAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/ManageBoundNetKeyAdapter.java similarity index 51% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AppKeyAdapter.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/ManageBoundNetKeyAdapter.java index 9de67bb33..f9860d80b 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/AppKeyAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/ManageBoundNetKeyAdapter.java @@ -20,53 +20,74 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.adapter; +package no.nordicsemi.android.nrfmeshprovisioner.keys.adapter; import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.RadioButton; import android.widget.TextView; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.NetworkKey; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; -public class AppKeyAdapter extends RecyclerView.Adapter { +public class ManageBoundNetKeyAdapter extends RecyclerView.Adapter { - private final SparseArray appKeys; + private final List mNetworkKeys; private final Context mContext; + private ApplicationKey mAppKey; private OnItemClickListener mOnItemClickListener; - public AppKeyAdapter(final Context context, final SparseArray appKeys) { - this.mContext = context; - this.appKeys = appKeys; + public ManageBoundNetKeyAdapter(@NonNull final Context context, + @NonNull final List networkKeys, + @NonNull final ApplicationKey appKey) { + mContext = context; + mNetworkKeys = networkKeys; + mAppKey = appKey; } - public void setOnItemClickListener(final AppKeyAdapter.OnItemClickListener listener) { + public void setOnItemClickListener(final ManageBoundNetKeyAdapter.OnItemClickListener listener) { mOnItemClickListener = listener; } + @NonNull @Override - public AppKeyAdapter.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { - final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.app_key_item, parent, false); - return new AppKeyAdapter.ViewHolder(layoutView); + public ManageBoundNetKeyAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.removable_row_item2, parent, false); + return new ManageBoundNetKeyAdapter.ViewHolder(layoutView); } @Override - public void onBindViewHolder(final AppKeyAdapter.ViewHolder holder, final int position) { - if(appKeys.size() > 0) { - holder.appKeyId.setText(mContext.getString(R.string.app_key_item , position + 1)); - final String appKey = MeshParserUtils.bytesToHex(appKeys.get(position).getKey(), false); - holder.appKey.setText(appKey.toUpperCase()); + public void onBindViewHolder(@NonNull final ManageBoundNetKeyAdapter.ViewHolder holder, final int position) { + if (mNetworkKeys.size() > 0) { + final NetworkKey networkKey = mNetworkKeys.get(position); + holder.netKeyName.setText(networkKey.getName()); + final String key = MeshParserUtils.bytesToHex(networkKey.getKey(), false); + holder.netKey.setText(key.toUpperCase()); + holder.getSwipeableView().setTag(networkKey); + + if (checkRadio(networkKey)) { + holder.bound.setChecked(true); + } else { + holder.bound.setChecked(false); + } } } + private boolean checkRadio(@NonNull final NetworkKey key) { + return key.getKeyIndex() == mAppKey.getBoundNetKeyIndex(); + } + @Override public long getItemId(final int position) { return position; @@ -74,7 +95,7 @@ public long getItemId(final int position) { @Override public int getItemCount() { - return appKeys.size(); + return mNetworkKeys.size(); } public boolean isEmpty() { @@ -83,23 +104,29 @@ public boolean isEmpty() { @FunctionalInterface public interface OnItemClickListener { - void onItemClick(final int position, final ApplicationKey appKey); + ApplicationKey updateBoundNetKeyIndex(final int position, @NonNull final NetworkKey networkKey); } final class ViewHolder extends RemovableViewHolder { - @BindView(R.id.app_key_id) - TextView appKeyId; - @BindView(R.id.app_key) - TextView appKey; + @BindView(R.id.title) + TextView netKeyName; + @BindView(R.id.subtitle) + TextView netKey; + @BindView(R.id.radio) + RadioButton bound; private ViewHolder(final View view) { super(view); ButterKnife.bind(this, view); view.findViewById(R.id.removable).setOnClickListener(v -> { if (mOnItemClickListener != null) { - final int key = appKeys.keyAt(getAdapterPosition()); - mOnItemClickListener.onItemClick(key, appKeys.get(key)); + final NetworkKey netKey = mNetworkKeys.get(getAdapterPosition()); + final ApplicationKey appKey = mOnItemClickListener.updateBoundNetKeyIndex(getAdapterPosition(), netKey); + if (appKey != null) { + mAppKey = appKey; + notifyDataSetChanged(); + } } }); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/BindAppKeyAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/ManageNetKeyAdapter.java similarity index 55% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/BindAppKeyAdapter.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/ManageNetKeyAdapter.java index 8f5179741..496854ac3 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/BindAppKeyAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/adapter/ManageNetKeyAdapter.java @@ -20,54 +20,69 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.adapter; +package no.nordicsemi.android.nrfmeshprovisioner.keys.adapter; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.NetworkKey; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.MeshNetworkLiveData; import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; -public class BindAppKeyAdapter extends RecyclerView.Adapter { +public class ManageNetKeyAdapter extends RecyclerView.Adapter { - private final List appKeys; + private final List networkKeys = new ArrayList<>(); private final Context mContext; private OnItemClickListener mOnItemClickListener; - public BindAppKeyAdapter(final Context context, final List appKeys) { + public ManageNetKeyAdapter(@NonNull final Context context, @NonNull final MeshNetworkLiveData meshNetworkLiveData) { this.mContext = context; - this.appKeys = appKeys; + meshNetworkLiveData.observe((LifecycleOwner) context, networkData -> { + final List keys = networkData.getNetworkKeys(); + if (keys != null) { + networkKeys.clear(); + networkKeys.addAll(keys); + networkKeys.remove(0); + Collections.sort(networkKeys, Utils.netKeyComparator); + } + notifyDataSetChanged(); + }); } - public void setOnItemClickListener(final BindAppKeyAdapter.OnItemClickListener listener) { + public void setOnItemClickListener(final ManageNetKeyAdapter.OnItemClickListener listener) { mOnItemClickListener = listener; } @NonNull @Override - public BindAppKeyAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.app_key_item, parent, false); - return new BindAppKeyAdapter.ViewHolder(layoutView); + public ManageNetKeyAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.removable_row_item, parent, false); + return new ManageNetKeyAdapter.ViewHolder(layoutView); } @Override - public void onBindViewHolder(@NonNull final BindAppKeyAdapter.ViewHolder holder, final int position) { - if(appKeys.size() > 0) { - final ApplicationKey applicationKey = appKeys.get(position); - holder.appKeyId.setText(mContext.getString(R.string.app_key_item , applicationKey.getKeyIndex())); - final String appKey = MeshParserUtils.bytesToHex(applicationKey.getKey(), false); - holder.appKey.setText(appKey.toUpperCase()); + public void onBindViewHolder(@NonNull final ManageNetKeyAdapter.ViewHolder holder, final int position) { + if (networkKeys.size() > 0) { + final NetworkKey networkKey = networkKeys.get(position); + holder.netKeyName.setText(networkKey.getName()); + final String key = MeshParserUtils.bytesToHex(networkKey.getKey(), false); + holder.netKey.setText(key.toUpperCase()); + holder.getSwipeableView().setTag(networkKey); } } @@ -78,7 +93,7 @@ public long getItemId(final int position) { @Override public int getItemCount() { - return appKeys.size(); + return networkKeys.size(); } public boolean isEmpty() { @@ -87,23 +102,23 @@ public boolean isEmpty() { @FunctionalInterface public interface OnItemClickListener { - void onItemClick(final int position, final ApplicationKey appKey); + void onItemClick(final int position, @NonNull final NetworkKey networkKey); } final class ViewHolder extends RemovableViewHolder { - @BindView(R.id.app_key_id) - TextView appKeyId; - @BindView(R.id.app_key) - TextView appKey; + @BindView(R.id.title) + TextView netKeyName; + @BindView(R.id.subtitle) + TextView netKey; private ViewHolder(final View view) { super(view); ButterKnife.bind(this, view); view.findViewById(R.id.removable).setOnClickListener(v -> { if (mOnItemClickListener != null) { - final int key = getAdapterPosition(); - mOnItemClickListener.onItemClick(key, appKeys.get(key)); + final NetworkKey key = networkKeys.get(getAdapterPosition()); + mOnItemClickListener.onItemClick(getAdapterPosition(), key); } }); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAddAppKey.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentAddKey.java similarity index 72% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAddAppKey.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentAddKey.java index 98d404d0e..33a60abab 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentAddAppKey.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentAddKey.java @@ -20,34 +20,33 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs; -import android.app.AlertDialog; +import android.annotation.SuppressLint; +import androidx.appcompat.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; +import android.widget.TextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.meshprovisioner.utils.SecureUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.AppKeyAdapter; - -public class DialogFragmentAddAppKey extends DialogFragment implements AppKeyAdapter.OnItemClickListener { - private static final String APP_KEY = "APP_KEY"; +public class DialogFragmentAddKey extends DialogFragment { //UI Bindings @BindView(R.id.text_input_layout) @@ -57,28 +56,30 @@ public class DialogFragmentAddAppKey extends DialogFragment implements AppKeyAda private String mAppKey; - public static DialogFragmentAddAppKey newInstance(final String appKey) { - DialogFragmentAddAppKey fragmentNetworkKey = new DialogFragmentAddAppKey(); + public interface DialogFragmentAddAppKeysListener { + void onAppKeyAdded(@NonNull final String appKey); + } + + public static DialogFragmentAddKey newInstance() { + DialogFragmentAddKey fragmentAddAppKey = new DialogFragmentAddKey(); final Bundle args = new Bundle(); - args.putString(APP_KEY, appKey); - fragmentNetworkKey.setArguments(args); - return fragmentNetworkKey; + fragmentAddAppKey.setArguments(args); + return fragmentAddAppKey; } @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (getArguments() != null) { - mAppKey = getArguments().getString(APP_KEY); - } + mAppKey = SecureUtils.generateRandomApplicationKey(); } @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_key_input, null); ButterKnife.bind(this, rootView); - + final TextView summary = rootView.findViewById(R.id.summary); //Bind ui appKeysInputLayout.setHint(getString(R.string.hint_app_key)); appKeyInput.setText(mAppKey); @@ -103,33 +104,34 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null) - .setNeutralButton(R.string.generate_app_key, null); + .setNeutralButton(R.string.generate_new_key, null); alertDialogBuilder.setIcon(R.drawable.ic_vpn_key_black_alpha_24dp); alertDialogBuilder.setTitle(R.string.title_manage_app_keys); - alertDialogBuilder.setMessage(R.string.summary_app_keys); + summary.setText(R.string.title_app_keys); final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String appKey = appKeyInput.getText().toString(); + final String appKey = appKeyInput.getEditableText().toString().trim(); if (validateInput(appKey)) { try { - ((DialogFragmentAddAppKeysListener) getContext()).onAppKeyAdded(appKey); + ((DialogFragmentAddAppKeysListener) requireContext()).onAppKeyAdded(appKey); dismiss(); } catch (IllegalArgumentException ex) { appKeysInputLayout.setError(ex.getMessage()); } } }); - alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> appKeyInput.setText(SecureUtils.generateRandomNetworkKey())); + alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL). + setOnClickListener(v -> appKeyInput.setText(SecureUtils.generateRandomNetworkKey())); return alertDialog; } private boolean validateInput(final String appKey) { try { - if(MeshParserUtils.validateAppKeyInput(getContext(), appKey)) { + if(MeshParserUtils.validateAppKeyInput(appKey)) { return true; } } catch (IllegalArgumentException ex) { @@ -137,13 +139,4 @@ private boolean validateInput(final String appKey) { } return false; } - - @Override - public void onItemClick(final int position, final ApplicationKey appKey) { - - } - - public interface DialogFragmentAddAppKeysListener { - void onAppKeyAdded(final String appKey); - } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentEditAppKey.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentEditAppKey.java similarity index 69% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentEditAppKey.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentEditAppKey.java index dddab732b..fe663359d 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentEditAppKey.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentEditAppKey.java @@ -20,31 +20,37 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; +import android.text.method.KeyListener; import android.view.LayoutInflater; import android.view.View; +import android.widget.TextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.meshprovisioner.utils.SecureUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.AppKeyAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.keys.MeshKeyListener; +import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; -public class DialogFragmentEditAppKey extends DialogFragment implements AppKeyAdapter.OnItemClickListener { +public class DialogFragmentEditAppKey extends DialogFragment { private static final String POSITION = "POSITION"; private static final String APP_KEY = "APP_KEY"; @@ -58,7 +64,7 @@ public class DialogFragmentEditAppKey extends DialogFragment implements AppKeyAd private int mPosition; private ApplicationKey mAppKey; - public static DialogFragmentEditAppKey newInstance(final int position, final ApplicationKey appKey) { + public static DialogFragmentEditAppKey newInstance(final int position, @NonNull final ApplicationKey appKey) { DialogFragmentEditAppKey fragmentNetworkKey = new DialogFragmentEditAppKey(); final Bundle args = new Bundle(); args.putInt(POSITION, position); @@ -79,12 +85,16 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_key_input, null); - ButterKnife.bind(this, rootView); + ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); //Bind ui + final KeyListener hexKeyListener = new HexKeyListener(); appKeysInputLayout.setHint(getString(R.string.hint_app_key)); appKeyInput.setText(MeshParserUtils.bytesToHex(mAppKey.getKey(), false)); + appKeyInput.setKeyListener(hexKeyListener); appKeyInput.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { @@ -106,47 +116,30 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()) + .setView(rootView) + .setIcon(R.drawable.ic_vpn_key_black_alpha_24dp) + .setTitle(R.string.title_edit_key) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .setNeutralButton(R.string.generate_new_key, null); + final AlertDialog alertDialog = alertDialogBuilder.show(); - alertDialogBuilder.setIcon(R.drawable.ic_vpn_key_black_alpha_24dp); - alertDialogBuilder.setTitle(R.string.title_manage_app_keys); - alertDialogBuilder.setMessage(R.string.summary_app_keys); + summary.setText(R.string.summary_edit_key); - final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String appKey = appKeyInput.getText().toString(); - if (validateInput(appKey)) { - try { - ((DialogFragmentEditAppKeysListener) getContext()).onAppKeysUpdated(mPosition, appKey); + final String appKey = appKeyInput.getEditableText().toString().trim(); + try { + if (((MeshKeyListener) requireContext()).onKeyUpdated(mPosition, appKey)) dismiss(); - } catch (IllegalArgumentException ex) { - appKeysInputLayout.setError(ex.getMessage()); - } + } catch (IllegalArgumentException ex) { + appKeysInputLayout.setError(ex.getMessage()); } + }); + alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL). + setOnClickListener(v -> appKeyInput.setText(SecureUtils.generateRandomNetworkKey())); return alertDialog; } - - private boolean validateInput(final String appKey) { - try { - - if(MeshParserUtils.validateAppKeyInput(getContext(), appKey)) { - return true; - } - } catch (IllegalArgumentException ex) { - appKeysInputLayout.setError(ex.getMessage()); - } - return false; - } - - @Override - public void onItemClick(final int position, final ApplicationKey appKey) { - - } - - public interface DialogFragmentEditAppKeysListener { - void onAppKeysUpdated(final int position, final String appKey); - } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNetworkKey.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentEditNetKey.java similarity index 63% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNetworkKey.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentEditNetKey.java index ca1500bbc..41743d79c 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNetworkKey.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentEditNetKey.java @@ -20,38 +20,40 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.KeyListener; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.widget.TextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.transport.NetworkKey; +import no.nordicsemi.android.meshprovisioner.NetworkKey; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.meshprovisioner.utils.SecureUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.keys.MeshKeyListener; import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; -public class DialogFragmentNetworkKey extends DialogFragment { +public class DialogFragmentEditNetKey extends DialogFragment { - private static final String TAG = DialogFragmentNetworkKey.class.getSimpleName(); - private static final String PATTERN_NETWORK_KEY = "[0-9a-fA-F]{32}"; + private static final String TAG = DialogFragmentEditNetKey.class.getSimpleName(); + private static final String POSITION = "POSITION"; private static final String NETWORK_KEY = "NETWORK_KEY"; //UI Bindings @@ -60,11 +62,13 @@ public class DialogFragmentNetworkKey extends DialogFragment { @BindView(R.id.text_input) TextInputEditText networkKeyInput; + private int mPosition; private NetworkKey mNetworkKey; - public static DialogFragmentNetworkKey newInstance(final NetworkKey networkKey) { - DialogFragmentNetworkKey fragmentNetworkKey = new DialogFragmentNetworkKey(); + public static DialogFragmentEditNetKey newInstance(final int position, @NonNull final NetworkKey networkKey) { + DialogFragmentEditNetKey fragmentNetworkKey = new DialogFragmentEditNetKey(); final Bundle args = new Bundle(); + args.putInt(POSITION, position); args.putParcelable(NETWORK_KEY, networkKey); fragmentNetworkKey.setArguments(args); return fragmentNetworkKey; @@ -74,6 +78,7 @@ public static DialogFragmentNetworkKey newInstance(final NetworkKey networkKey) public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { + mPosition = getArguments().getInt(POSITION); mNetworkKey = getArguments().getParcelable(NETWORK_KEY); } } @@ -81,11 +86,12 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_key_input, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_key_input, null); final KeyListener hexKeyListener = new HexKeyListener(); //Bind ui ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); final String key = MeshParserUtils.bytesToHex(mNetworkKey.getKey(), false); networkKeyInputLayout.setHint(getString(R.string.hint_network_key)); networkKeyInput.setKeyListener(hexKeyListener); @@ -112,55 +118,27 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null) - .setNeutralButton(R.string.generate_network_key, null); + .setNeutralButton(R.string.generate_network_key, null) + .setIcon(R.drawable.ic_vpn_key_black_alpha_24dp) + .setTitle(R.string.title_edit_key); + final AlertDialog alertDialog = alertDialogBuilder.show(); - alertDialogBuilder.setIcon(R.drawable.ic_vpn_key_black_alpha_24dp); - alertDialogBuilder.setTitle(R.string.title_generate_network_key); - alertDialogBuilder.setMessage(R.string.summary_generate_network_key); + summary.setText(R.string.summary_edit_key); - final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String networkKey = networkKeyInput.getText().toString(); - if (validateInput(networkKey)) { - try { - if (getParentFragment() == null) { - ((DialogFragmentNetworkKeyListener) getActivity()).onNetworkKeyGenerated(networkKey); - } else { - ((DialogFragmentNetworkKeyListener) getParentFragment()).onNetworkKeyGenerated(networkKey); - } - } catch (Exception ex) { - Log.v(TAG, ex.getMessage()); - } - dismiss(); + final String networkKey = networkKeyInput.getEditableText().toString().trim(); + try { + if (((MeshKeyListener) requireActivity()).onKeyUpdated(mPosition, networkKey)) + dismiss(); + } catch (Exception ex) { + networkKeyInputLayout.setError(ex.getMessage()); } }); - alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> networkKeyInput.setText(SecureUtils.generateRandomNetworkKey())); + alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL) + .setOnClickListener(v -> networkKeyInput.setText(SecureUtils.generateRandomNetworkKey())); return alertDialog; } - - private boolean validateInput(final String input) { - try { - - if (!input.matches(Utils.HEX_PATTERN)) { - networkKeyInputLayout.setError(getString(R.string.invalid_hex_value)); - return false; - } - - if (MeshParserUtils.validateNetworkKeyInput(getContext(), input)) { - return true; - } - } catch (IllegalArgumentException ex) { - networkKeyInputLayout.setError(ex.getMessage()); - } - return false; - } - - public interface DialogFragmentNetworkKeyListener { - - void onNetworkKeyGenerated(final String networkKey); - - } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentModelConfiguration.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentKeyName.java similarity index 56% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentModelConfiguration.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentKeyName.java index 5f88aa2fc..2b4bec4ea 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentModelConfiguration.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/keys/dialogs/DialogFragmentKeyName.java @@ -20,45 +20,47 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.keys.dialogs; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; -import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; +import android.widget.TextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; -import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.meshprovisioner.utils.SecureUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.keys.MeshKeyListener; + +public class DialogFragmentKeyName extends DialogFragment { -public class DialogFragmentModelConfiguration extends DialogFragment{ - private static final String APP_KEY = "APP_KEY"; + private static final String KEY_NAME = "NODE_NAME"; //UI Bindings @BindView(R.id.text_input_layout) - TextInputLayout appKeysInputLayout; + TextInputLayout keyNameInputLayout; @BindView(R.id.text_input) - TextInputEditText appKeyInput; + TextInputEditText keyNameInput; - private String mAppKey; + private String name; - public static DialogFragmentAddAppKey newInstance(final String appKey) { - DialogFragmentAddAppKey fragmentNetworkKey = new DialogFragmentAddAppKey(); + public static DialogFragmentKeyName newInstance(@NonNull final String name) { + DialogFragmentKeyName fragmentNetworkKey = new DialogFragmentKeyName(); final Bundle args = new Bundle(); - args.putString(APP_KEY, appKey); + args.putString(KEY_NAME, name); fragmentNetworkKey.setArguments(args); return fragmentNetworkKey; } @@ -67,21 +69,21 @@ public static DialogFragmentAddAppKey newInstance(final String appKey) { public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { - mAppKey = getArguments().getString(APP_KEY); + name = getArguments().getString(KEY_NAME); } } @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_provision_data_input, null); - ButterKnife.bind(this, rootView); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_name, null); //Bind ui - appKeyInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS); - appKeysInputLayout.setHint(getString(R.string.hint_app_key)); - appKeyInput.setText(mAppKey); - appKeyInput.addTextChangedListener(new TextWatcher() { + ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); + keyNameInputLayout.setHint(getString(R.string.name)); + keyNameInput.setText(name); + keyNameInput.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { @@ -90,9 +92,9 @@ public void beforeTextChanged(final CharSequence s, final int start, final int c @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { if (TextUtils.isEmpty(s.toString())) { - appKeysInputLayout.setError(getString(R.string.error_empty_app_key)); + keyNameInputLayout.setError(getString(R.string.error_empty_name)); } else { - appKeysInputLayout.setError(null); + keyNameInputLayout.setError(null); } } @@ -102,41 +104,25 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null) - .setNeutralButton(R.string.generate_app_key, null); + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) + .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); alertDialogBuilder.setIcon(R.drawable.ic_vpn_key_black_alpha_24dp); - alertDialogBuilder.setTitle(R.string.title_manage_app_keys); - alertDialogBuilder.setMessage(R.string.summary_app_keys); + alertDialogBuilder.setTitle(R.string.title_edit_key_name); + summary.setText(R.string.key_name_rationale); final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String appKey = appKeyInput.getText().toString(); - if (validateInput(appKey)) { - ((DialogFragmentAddAppKey.DialogFragmentAddAppKeysListener) getContext()).onAppKeyAdded(appKey); - dismiss(); + final String nodeName = keyNameInput.getEditableText().toString().trim(); + try { + if (((MeshKeyListener) requireContext()).onKeyNameUpdated(nodeName)) { + dismiss(); + } + } catch (IllegalArgumentException ex) { + keyNameInputLayout.setError(ex.getMessage()); } }); - alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> { - appKeyInput.setText(SecureUtils.generateRandomNetworkKey()); - }); return alertDialog; } - - private boolean validateInput(final String appKey) { - try { - if(MeshParserUtils.validateAppKeyInput(getContext(), appKey)) { - return true; - } - } catch (IllegalArgumentException ex) { - appKeysInputLayout.setError(ex.getMessage()); - } - return false; - } - - public interface DialogFragmentAddAppKeysListener { - void onAppKeyAdded(final String appKey); - } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/BaseModelConfigurationActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/BaseModelConfigurationActivity.java new file mode 100644 index 000000000..1d81907be --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/BaseModelConfigurationActivity.java @@ -0,0 +1,801 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.node; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.google.android.material.snackbar.Snackbar; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.Group; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.models.ConfigurationClientModel; +import no.nordicsemi.android.meshprovisioner.models.ConfigurationServerModel; +import no.nordicsemi.android.meshprovisioner.models.SigModel; +import no.nordicsemi.android.meshprovisioner.models.SigModelParser; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelAppBind; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelAppStatus; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelAppUnbind; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelPublicationGet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelPublicationSet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelPublicationStatus; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelSubscriptionAdd; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelSubscriptionDelete; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelSubscriptionStatus; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelSubscriptionVirtualAddressAdd; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelSubscriptionVirtualAddressDelete; +import no.nordicsemi.android.meshprovisioner.transport.ConfigSigModelAppGet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigSigModelAppList; +import no.nordicsemi.android.meshprovisioner.transport.ConfigSigModelSubscriptionGet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigSigModelSubscriptionList; +import no.nordicsemi.android.meshprovisioner.transport.ConfigVendorModelAppGet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigVendorModelAppList; +import no.nordicsemi.android.meshprovisioner.transport.ConfigVendorModelSubscriptionGet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigVendorModelSubscriptionList; +import no.nordicsemi.android.meshprovisioner.transport.Element; +import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; +import no.nordicsemi.android.meshprovisioner.transport.MeshModel; +import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import no.nordicsemi.android.meshprovisioner.transport.PublicationSettings; +import no.nordicsemi.android.meshprovisioner.utils.CompositionDataParser; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.GroupCallbacks; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.adapter.GroupAddressAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentConfigStatus; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentDisconnected; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentError; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentGroupSubscription; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentTransactionStatus; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.adapter.BoundAppKeysAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ModelConfigurationViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; + +public abstract class BaseModelConfigurationActivity extends AppCompatActivity implements Injectable, + GroupCallbacks, + ItemTouchHelperAdapter, + DialogFragmentDisconnected.DialogFragmentDisconnectedListener, SwipeRefreshLayout.OnRefreshListener { + + private static final String DIALOG_FRAGMENT_CONFIGURATION_STATUS = "DIALOG_FRAGMENT_CONFIGURATION_STATUS"; + private static final String PROGRESS_BAR_STATE = "PROGRESS_BAR_STATE"; + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + @BindView(R.id.container) + CoordinatorLayout mContainer; + @BindView(R.id.app_key_card) + View mContainerAppKeyBinding; + @BindView(R.id.action_bind_app_key) + Button mActionBindAppKey; + @BindView(R.id.bound_keys) + TextView mAppKeyView; + @BindView(R.id.unbind_hint) + TextView mUnbindHint; + + @BindView(R.id.publish_address_card) + View mContainerPublication; + @BindView(R.id.action_set_publication) + Button mActionSetPublication; + @BindView(R.id.action_clear_publication) + Button mActionClearPublication; + @BindView(R.id.publish_address) + TextView mPublishAddressView; + + @BindView(R.id.subscription_address_card) + View mContainerSubscribe; + @BindView(R.id.action_subscribe_address) + Button mActionSubscribe; + @BindView(R.id.subscribe_addresses) + TextView mSubscribeAddressView; + @BindView(R.id.subscribe_hint) + TextView mSubscribeHint; + @BindView(R.id.configuration_progress_bar) + ProgressBar mProgressbar; + @BindView(R.id.swipe_refresh) + SwipeRefreshLayout mSwipe; + + protected Handler mHandler; + protected ModelConfigurationViewModel mViewModel; + protected List mGroupAddress = new ArrayList<>(); + protected List mKeyIndexes = new ArrayList<>(); + protected GroupAddressAdapter mSubscriptionAdapter; + protected BoundAppKeysAdapter mBoundAppKeyAdapter; + protected Button mActionRead; + protected Button mActionSetRelayState; + protected Button mReadNetworkTransmitStateButton; + protected Button mSetNetworkTransmitStateButton; + + private RecyclerView recyclerViewBoundKeys, recyclerViewAddresses; + protected boolean mIsConnected; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_model_configuration); + ButterKnife.bind(this); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(ModelConfigurationViewModel.class); + mHandler = new Handler(); + + final MeshModel meshModel = mViewModel.getSelectedModel().getValue(); + //noinspection ConstantConditions + final String modelName = meshModel.getModelName(); + + // Set up views + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(modelName); + final int modelId = meshModel.getModelId(); + getSupportActionBar().setSubtitle(getString(R.string.model_id, CompositionDataParser.formatModelIdentifier(modelId, true))); + mSwipe.setOnRefreshListener(this); + + recyclerViewAddresses = findViewById(R.id.recycler_view_addresses); + recyclerViewAddresses.setLayoutManager(new LinearLayoutManager(this)); + final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); + final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); + itemTouchHelper.attachToRecyclerView(recyclerViewAddresses); + mSubscriptionAdapter = new GroupAddressAdapter(this, mViewModel.getNetworkLiveData().getMeshNetwork(), mViewModel.getSelectedModel()); + recyclerViewAddresses.setAdapter(mSubscriptionAdapter); + + recyclerViewBoundKeys = findViewById(R.id.recycler_view_bound_keys); + recyclerViewBoundKeys.setLayoutManager(new LinearLayoutManager(this)); + final ItemTouchHelper.Callback itemTouchHelperCallbackKeys = new RemovableItemTouchHelperCallback(this); + final ItemTouchHelper itemTouchHelperKeys = new ItemTouchHelper(itemTouchHelperCallbackKeys); + itemTouchHelperKeys.attachToRecyclerView(recyclerViewBoundKeys); + mBoundAppKeyAdapter = new BoundAppKeysAdapter(this, mViewModel.getNetworkLiveData().getAppKeys(), mViewModel.getSelectedModel()); + recyclerViewBoundKeys.setAdapter(mBoundAppKeyAdapter); + + mActionBindAppKey.setOnClickListener(v -> { + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + if (node != null && !node.isExist(SigModelParser.CONFIGURATION_SERVER)) { + return; + } + if (!checkConnectivity()) return; + final Intent bindAppKeysIntent = new Intent(BaseModelConfigurationActivity.this, AppKeysActivity.class); + bindAppKeysIntent.putExtra(Utils.EXTRA_DATA, Utils.BIND_APP_KEY); + startActivityForResult(bindAppKeysIntent, Utils.SELECT_KEY); + }); + + mPublishAddressView.setText(R.string.none); + mActionSetPublication.setOnClickListener(v -> navigateToPublication()); + + mActionClearPublication.setOnClickListener(v -> clearPublication()); + + mActionSubscribe.setOnClickListener(v -> { + if (!checkConnectivity()) return; + //noinspection ConstantConditions + final ArrayList groups = new ArrayList<>(mViewModel.getGroups().getValue()); + final DialogFragmentGroupSubscription fragmentSubscriptionAddress = DialogFragmentGroupSubscription.newInstance(groups); + fragmentSubscriptionAddress.show(getSupportFragmentManager(), null); + }); + + mViewModel.getSelectedModel().observe(this, model -> { + if (model != null) { + updateAppStatusUi(model); + updatePublicationUi(model); + updateSubscriptionUi(model); + } + }); + + mViewModel.getTransactionStatus().observe(this, transactionStatus -> { + if (transactionStatus != null) { + hideProgressBar(); + final String message = getString(R.string.operation_timed_out); + DialogFragmentTransactionStatus fragmentMessage = DialogFragmentTransactionStatus.newInstance("Transaction Failed", message); + fragmentMessage.show(getSupportFragmentManager(), null); + } + }); + + mViewModel.isConnectedToProxy().observe(this, isConnected -> { + if (isConnected != null) { + mIsConnected = isConnected; + hideProgressBar(); + updateClickableViews(); + } + invalidateOptionsMenu(); + }); + + mViewModel.getMeshMessage().observe(this, this::updateMeshMessage); + + final Boolean isConnectedToNetwork = mViewModel.isConnectedToProxy().getValue(); + if (isConnectedToNetwork != null) { + mIsConnected = isConnectedToNetwork; + } + invalidateOptionsMenu(); + } + + @Override + protected void onStart() { + super.onStart(); + mViewModel.setActivityVisible(true); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + if (mIsConnected) { + getMenuInflater().inflate(R.menu.disconnect, menu); + } else { + getMenuInflater().inflate(R.menu.connect, menu); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + case R.id.action_connect: + mViewModel.navigateToScannerActivity(this, false, Utils.CONNECT_TO_NETWORK, false); + return true; + case R.id.action_disconnect: + mViewModel.disconnect(); + return true; + default: + return false; + } + } + + @Override + protected void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(PROGRESS_BAR_STATE, mProgressbar.getVisibility() == View.VISIBLE); + } + + @Override + protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + if (savedInstanceState.getBoolean(PROGRESS_BAR_STATE)) { + mProgressbar.setVisibility(View.VISIBLE); + disableClickableViews(); + } else { + mProgressbar.setVisibility(View.INVISIBLE); + enableClickableViews(); + } + } + + @Override + protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case Utils.SELECT_KEY: + if (resultCode == RESULT_OK) { + final ApplicationKey appKey = data.getParcelableExtra(AppKeysActivity.RESULT_APP_KEY); + if (appKey != null) { + bindAppKey(appKey.getKeyIndex()); + } + } + break; + case PublicationSettingsActivity.SET_PUBLICATION_SETTINGS: + if (resultCode == RESULT_OK) { + showProgressbar(); + } + break; + } + } + + @Override + protected void onStop() { + super.onStop(); + mViewModel.setActivityVisible(false); + if (isFinishing()) { + mHandler.removeCallbacksAndMessages(null); + } + } + + @Override + public Group createGroup(@NonNull final String name) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + return network.createGroup(network.getSelectedProvisioner(), name); + } + + @Override + public Group createGroup(@NonNull final UUID uuid, final String name) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + return network.createGroup(uuid, null, name); + } + + @Override + public boolean onGroupAdded(@NonNull final String name, final int address) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + final Group group = network.createGroup(network.getSelectedProvisioner(), address, name); + if (group != null) { + if (network.addGroup(group)) { + subscribe(group); + return true; + } + } + return false; + } + + @Override + public boolean onGroupAdded(@NonNull final Group group) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network.addGroup(group)) { + subscribe(group); + return true; + } + return false; + } + + @Override + public void subscribe(final Group group) { + final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); + if (meshNode != null) { + final Element element = mViewModel.getSelectedElement().getValue(); + if (element != null) { + final int elementAddress = element.getElementAddress(); + final MeshModel model = mViewModel.getSelectedModel().getValue(); + if (model != null) { + final int modelIdentifier = model.getModelId(); + final MeshMessage configModelSubscriptionAdd; + if (group.getAddressLabel() == null) { + configModelSubscriptionAdd = new ConfigModelSubscriptionAdd(elementAddress, group.getAddress(), modelIdentifier); + } else { + configModelSubscriptionAdd = new ConfigModelSubscriptionVirtualAddressAdd(elementAddress, group.getAddressLabel(), modelIdentifier); + } + sendMessage(meshNode.getUnicastAddress(), configModelSubscriptionAdd); + } + } + } + } + + @Override + public void onItemDismiss(final RemovableViewHolder viewHolder) { + final int position = viewHolder.getAdapterPosition(); + if (viewHolder instanceof GroupAddressAdapter.ViewHolder) { + deleteSubscription(position); + } else if (viewHolder instanceof BoundAppKeysAdapter.ViewHolder) { + unbindAppKey(position); + } + } + + @Override + public void onItemDismissFailed(final RemovableViewHolder viewHolder) { + } + + @Override + public void onDisconnected() { + finish(); + } + + @Override + public void onRefresh() { + final MeshModel model = mViewModel.getSelectedModel().getValue(); + if (!checkConnectivity() || model == null) { + mSwipe.setRefreshing(false); + } + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + final Element element = mViewModel.getSelectedElement().getValue(); + if (node != null && element != null && model != null) { + if (model instanceof SigModel) { + if (!(model instanceof ConfigurationServerModel) && !(model instanceof ConfigurationClientModel)) { + mViewModel.displaySnackBar(this, mContainer, getString(R.string.listing_model_configuration), Snackbar.LENGTH_LONG); + final ConfigSigModelAppGet appGet = new ConfigSigModelAppGet(element.getElementAddress(), model.getModelId()); + final ConfigSigModelSubscriptionGet subscriptionGet = new ConfigSigModelSubscriptionGet(element.getElementAddress(), model.getModelId()); + mViewModel.getMessageQueue().add(appGet); + mViewModel.getMessageQueue().add(subscriptionGet); + queuePublicationGetMessage(element.getElementAddress(), model.getModelId()); + //noinspection ConstantConditions + sendMessage(node.getUnicastAddress(), mViewModel.getMessageQueue().peek()); + } else { + mSwipe.setRefreshing(false); + } + + } else { + mViewModel.displaySnackBar(this, mContainer, getString(R.string.listing_model_configuration), Snackbar.LENGTH_LONG); + final ConfigVendorModelAppGet appGet = new ConfigVendorModelAppGet(element.getElementAddress(), model.getModelId()); + final ConfigVendorModelSubscriptionGet subscriptionGet = new ConfigVendorModelSubscriptionGet(element.getElementAddress(), model.getModelId()); + mViewModel.getMessageQueue().add(appGet); + mViewModel.getMessageQueue().add(subscriptionGet); + queuePublicationGetMessage(element.getElementAddress(), model.getModelId()); + //noinspection ConstantConditions + sendMessage(node.getUnicastAddress(), mViewModel.getMessageQueue().peek()); + } + } + } + + protected void navigateToPublication() { + final MeshModel model = mViewModel.getSelectedModel().getValue(); + if (model != null && !model.getBoundAppKeyIndexes().isEmpty()) { + final Intent publicationSettings = new Intent(this, PublicationSettingsActivity.class); + startActivityForResult(publicationSettings, PublicationSettingsActivity.SET_PUBLICATION_SETTINGS); + } else { + mViewModel.displaySnackBar(this, mContainer, getString(R.string.error_no_app_keys_bound), Snackbar.LENGTH_LONG); + } + } + + private void bindAppKey(final int appKeyIndex) { + final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); + if (meshNode != null) { + final Element element = mViewModel.getSelectedElement().getValue(); + if (element != null) { + final MeshModel model = mViewModel.getSelectedModel().getValue(); + if (model != null) { + final ConfigModelAppBind configModelAppUnbind = new ConfigModelAppBind(element.getElementAddress(), model.getModelId(), appKeyIndex); + sendMessage(meshNode.getUnicastAddress(), configModelAppUnbind); + } + } + } + } + + private void unbindAppKey(final int position) { + if (mBoundAppKeyAdapter.getItemCount() != 0) { + if (!checkConnectivity()) { + mBoundAppKeyAdapter.notifyItemChanged(position); + return; + } + final ApplicationKey appKey = mBoundAppKeyAdapter.getAppKey(position); + final int keyIndex = appKey.getKeyIndex(); + final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); + if (meshNode != null) { + final Element element = mViewModel.getSelectedElement().getValue(); + if (element != null) { + final MeshModel model = mViewModel.getSelectedModel().getValue(); + if (model != null) { + final ConfigModelAppUnbind configModelAppUnbind = new ConfigModelAppUnbind(element.getElementAddress(), model.getModelId(), keyIndex); + sendMessage(meshNode.getUnicastAddress(), configModelAppUnbind); + } + } + } + } + } + + private void clearPublication() { + final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); + if (meshNode != null) { + final Element element = mViewModel.getSelectedElement().getValue(); + if (element != null) { + final MeshModel model = mViewModel.getSelectedModel().getValue(); + if (model != null) { + if (!model.getBoundAppKeyIndexes().isEmpty()) { + final int address = MeshAddress.UNASSIGNED_ADDRESS; + final int appKeyIndex = model.getPublicationSettings().getAppKeyIndex(); + final boolean credentialFlag = model.getPublicationSettings().getCredentialFlag(); + final int ttl = model.getPublicationSettings().getPublishTtl(); + final int publicationSteps = model.getPublicationSettings().getPublicationSteps(); + final int publicationResolution = model.getPublicationSettings().getPublicationResolution(); + final int retransmitCount = model.getPublicationSettings().getPublishRetransmitCount(); + final int retransmitIntervalSteps = model.getPublicationSettings().getPublishRetransmitIntervalSteps(); + final ConfigModelPublicationSet configModelPublicationSet = new ConfigModelPublicationSet(element.getElementAddress(), address, appKeyIndex, + credentialFlag, ttl, publicationSteps, publicationResolution, retransmitCount, retransmitIntervalSteps, model.getModelId()); + sendMessage(meshNode.getUnicastAddress(), configModelPublicationSet); + } else { + mViewModel.displaySnackBar(this, mContainer, getString(R.string.error_no_app_keys_bound), Snackbar.LENGTH_LONG); + } + } + } + } + } + + private void deleteSubscription(final int position) { + if (mSubscriptionAdapter.getItemCount() != 0) { + if (!checkConnectivity()) { + mSubscriptionAdapter.notifyItemChanged(position); + return; + } + final int address = mGroupAddress.get(position); + final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); + if (meshNode != null) { + final Element element = mViewModel.getSelectedElement().getValue(); + if (element != null) { + final MeshModel model = mViewModel.getSelectedModel().getValue(); + if (model != null) { + MeshMessage subscriptionDelete = null; + if (MeshAddress.isValidGroupAddress(address)) { + subscriptionDelete = new ConfigModelSubscriptionDelete(element.getElementAddress(), address, model.getModelId()); + } else { + final UUID uuid = model.getLabelUUID(address); + if (uuid != null) + subscriptionDelete = new ConfigModelSubscriptionVirtualAddressDelete(element.getElementAddress(), uuid, model.getModelId()); + } + + if (subscriptionDelete != null) { + sendMessage(meshNode.getUnicastAddress(), subscriptionDelete); + } + } + } + } + } + } + + protected final void showProgressbar() { + mHandler.postDelayed(mOperationTimeout, Utils.MESSAGE_TIME_OUT); + disableClickableViews(); + mProgressbar.setVisibility(View.VISIBLE); + } + + protected final void hideProgressBar() { + mSwipe.setRefreshing(false); + enableClickableViews(); + mProgressbar.setVisibility(View.INVISIBLE); + mHandler.removeCallbacks(mOperationTimeout); + } + + private final Runnable mOperationTimeout = () -> { + hideProgressBar(); + mViewModel.getMessageQueue().clear(); + if (mViewModel.isActivityVisibile()) { + DialogFragmentTransactionStatus fragmentMessage = DialogFragmentTransactionStatus. + newInstance(getString(R.string.title_transaction_failed), getString(R.string.operation_timed_out)); + fragmentMessage.show(getSupportFragmentManager(), null); + } + }; + + protected void enableClickableViews() { + mActionBindAppKey.setEnabled(true); + mActionSetPublication.setEnabled(true); + mActionClearPublication.setEnabled(true); + mActionSubscribe.setEnabled(true); + + if (mActionSetRelayState != null) + mActionSetRelayState.setEnabled(true); + if (mReadNetworkTransmitStateButton != null) + mReadNetworkTransmitStateButton.setEnabled(true); + if (mSetNetworkTransmitStateButton != null) + mSetNetworkTransmitStateButton.setEnabled(true); + + if (mActionRead != null && !mActionRead.isEnabled()) + mActionRead.setEnabled(true); + } + + protected void disableClickableViews() { + mActionBindAppKey.setEnabled(false); + mActionSetPublication.setEnabled(false); + mActionClearPublication.setEnabled(false); + mActionSubscribe.setEnabled(false); + + if (mActionSetRelayState != null) + mActionSetRelayState.setEnabled(false); + if (mReadNetworkTransmitStateButton != null) + mReadNetworkTransmitStateButton.setEnabled(false); + if (mSetNetworkTransmitStateButton != null) + mSetNetworkTransmitStateButton.setEnabled(false); + + if (mActionRead != null) + mActionRead.setEnabled(false); + } + + /** + * Update the mesh message + * + * @param meshMessage {@link MeshMessage} mesh message status + */ + protected void updateMeshMessage(final MeshMessage meshMessage) { + if (meshMessage instanceof ConfigModelAppStatus) { + final ConfigModelAppStatus status = (ConfigModelAppStatus) meshMessage; + if (status.isSuccessful()) { + mViewModel.displaySnackBar(this, mContainer, getString(R.string.operation_success), Snackbar.LENGTH_SHORT); + } else { + displayStatusDialogFragment(getString(R.string.title_appkey_status), status.getStatusCodeName()); + } + } else if (meshMessage instanceof ConfigSigModelAppList) { + final ConfigSigModelAppList status = (ConfigSigModelAppList) meshMessage; + mViewModel.removeMessage(); + if (status.isSuccessful()) { + if (handleStatuses()) return; + } else { + displayStatusDialogFragment(getString(R.string.title_sig_model_subscription_list), status.getStatusCodeName()); + } + } else if (meshMessage instanceof ConfigVendorModelAppList) { + final ConfigVendorModelAppList status = (ConfigVendorModelAppList) meshMessage; + mViewModel.removeMessage(); + if (status.isSuccessful()) { + if (handleStatuses()) return; + } else { + displayStatusDialogFragment(getString(R.string.title_vendor_model_app_list), status.getStatusCodeName()); + } + } else if (meshMessage instanceof ConfigModelPublicationStatus) { + final ConfigModelPublicationStatus status = (ConfigModelPublicationStatus) meshMessage; + mViewModel.removeMessage(); + if (status.isSuccessful()) { + if (handleStatuses()) return; + } else { + displayStatusDialogFragment(getString(R.string.title_publication_status), status.getStatusCodeName()); + } + } else if (meshMessage instanceof ConfigModelSubscriptionStatus) { + final ConfigModelSubscriptionStatus status = (ConfigModelSubscriptionStatus) meshMessage; + mViewModel.removeMessage(); + if (status.isSuccessful()) { + if (handleStatuses()) return; + } else { + displayStatusDialogFragment(getString(R.string.title_subscription_status), status.getStatusCodeName()); + } + } else if (meshMessage instanceof ConfigSigModelSubscriptionList) { + final ConfigSigModelSubscriptionList status = (ConfigSigModelSubscriptionList) meshMessage; + mViewModel.removeMessage(); + if (status.isSuccessful()) { + if (handleStatuses()) return; + } else { + displayStatusDialogFragment(getString(R.string.title_sig_model_subscription_list), status.getStatusCodeName()); + } + } else if (meshMessage instanceof ConfigVendorModelSubscriptionList) { + final ConfigVendorModelSubscriptionList status = (ConfigVendorModelSubscriptionList) meshMessage; + mViewModel.removeMessage(); + if (status.isSuccessful()) { + if (handleStatuses()) return; + } else { + displayStatusDialogFragment(getString(R.string.title_vendor_model_subscription_list), status.getStatusCodeName()); + } + } + hideProgressBar(); + } + + private void updateAppStatusUi(final MeshModel meshModel) { + final List keys = meshModel.getBoundAppKeyIndexes(); + mKeyIndexes.clear(); + mKeyIndexes.addAll(keys); + if (!keys.isEmpty()) { + mUnbindHint.setVisibility(View.VISIBLE); + mAppKeyView.setVisibility(View.GONE); + recyclerViewBoundKeys.setVisibility(View.VISIBLE); + } else { + mUnbindHint.setVisibility(View.GONE); + mAppKeyView.setVisibility(View.VISIBLE); + recyclerViewBoundKeys.setVisibility(View.GONE); + } + } + + private void updatePublicationUi(final MeshModel meshModel) { + final PublicationSettings publicationSettings = meshModel.getPublicationSettings(); + if (publicationSettings != null) { + final int publishAddress = publicationSettings.getPublishAddress(); + if (publishAddress != MeshAddress.UNASSIGNED_ADDRESS) { + if (MeshAddress.isValidVirtualAddress(publishAddress)) { + final UUID uuid = publicationSettings.getLabelUUID(); + if (uuid != null) { + mPublishAddressView.setText(uuid.toString().toUpperCase(Locale.US)); + } else { + mPublishAddressView.setText(MeshAddress.formatAddress(publishAddress, true)); + } + } else { + mPublishAddressView.setText(MeshAddress.formatAddress(publishAddress, true)); + } + mActionClearPublication.setVisibility(View.VISIBLE); + } else { + mPublishAddressView.setText(R.string.none); + mActionClearPublication.setVisibility(View.GONE); + } + } + } + + private void updateSubscriptionUi(final MeshModel meshModel) { + final List subscriptionAddresses = meshModel.getSubscribedAddresses(); + mGroupAddress.clear(); + mGroupAddress.addAll(subscriptionAddresses); + if (!subscriptionAddresses.isEmpty()) { + mSubscribeHint.setVisibility(View.VISIBLE); + mSubscribeAddressView.setVisibility(View.GONE); + recyclerViewAddresses.setVisibility(View.VISIBLE); + } else { + mSubscribeHint.setVisibility(View.GONE); + mSubscribeAddressView.setVisibility(View.VISIBLE); + recyclerViewAddresses.setVisibility(View.GONE); + } + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + protected final boolean checkConnectivity() { + if (!mIsConnected) { + mViewModel.displayDisconnectedSnackBar(this, mContainer); + return false; + } + return true; + } + + protected void sendMessage(@NonNull final MeshMessage meshMessage) { + try { + if (!checkConnectivity()) + return; + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + if (node != null) { + mViewModel.getMeshManagerApi().createMeshPdu(node.getUnicastAddress(), meshMessage); + showProgressbar(); + } + } catch (IllegalArgumentException ex) { + hideProgressBar(); + final DialogFragmentError message = DialogFragmentError. + newInstance(getString(R.string.title_error), ex.getMessage()); + message.show(getSupportFragmentManager(), null); + } + } + + private boolean handleStatuses() { + final MeshMessage message = mViewModel.getMessageQueue().peek(); + if (message != null) { + sendMessage(message); + return true; + } else { + mViewModel.displaySnackBar(this, mContainer, getString(R.string.operation_success), Snackbar.LENGTH_SHORT); + } + return false; + } + + protected void sendMessage(final int address, @NonNull final MeshMessage meshMessage) { + try { + if (!checkConnectivity()) + return; + mViewModel.getMeshManagerApi().createMeshPdu(address, meshMessage); + showProgressbar(); + } catch (IllegalArgumentException ex) { + hideProgressBar(); + final DialogFragmentError message = DialogFragmentError. + newInstance(getString(R.string.title_error), ex.getMessage()); + message.show(getSupportFragmentManager(), null); + } + } + + private void updateClickableViews() { + final MeshModel model = mViewModel.getSelectedModel().getValue(); + if (model != null && model.getModelId() == SigModelParser.CONFIGURATION_CLIENT) + disableClickableViews(); + } + + private void queuePublicationGetMessage(final int address, final int modelId) { + final ConfigModelPublicationGet publicationGet = new ConfigModelPublicationGet(address, modelId); + mViewModel.getMessageQueue().add(publicationGet); + } + + private void displayStatusDialogFragment(@NonNull final String title, @NonNull final String message) { + if (mViewModel.isActivityVisibile()) { + DialogFragmentConfigStatus fragmentAppKeyBindStatus = DialogFragmentConfigStatus. + newInstance(title, message); + fragmentAppKeyBindStatus.show(getSupportFragmentManager(), DIALOG_FRAGMENT_CONFIGURATION_STATUS); + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/ConfigurationClientActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/ConfigurationClientActivity.java new file mode 100644 index 000000000..478374672 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/ConfigurationClientActivity.java @@ -0,0 +1,19 @@ +package no.nordicsemi.android.nrfmeshprovisioner.node; + +import android.os.Bundle; + +import no.nordicsemi.android.meshprovisioner.models.ConfigurationClientModel; +import no.nordicsemi.android.meshprovisioner.transport.MeshModel; + + +public class ConfigurationClientActivity extends BaseModelConfigurationActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final MeshModel model = mViewModel.getSelectedModel().getValue(); + if (model instanceof ConfigurationClientModel) { + disableClickableViews(); + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ConfigurationServerActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/ConfigurationServerActivity.java similarity index 64% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ConfigurationServerActivity.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/ConfigurationServerActivity.java index a1ffff172..1143b6897 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ConfigurationServerActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/ConfigurationServerActivity.java @@ -1,15 +1,15 @@ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.constraint.ConstraintLayout; -import android.support.v7.widget.CardView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.constraintlayout.widget.ConstraintLayout; import no.nordicsemi.android.meshprovisioner.models.ConfigurationServerModel; import no.nordicsemi.android.meshprovisioner.transport.ConfigNetworkTransmitGet; import no.nordicsemi.android.meshprovisioner.transport.ConfigNetworkTransmitSet; @@ -22,8 +22,9 @@ import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; import no.nordicsemi.android.meshprovisioner.utils.NetworkTransmitSettings; import no.nordicsemi.android.meshprovisioner.utils.RelaySettings; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentNetworkTransmitSettings; -import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogRelayRetransmitSettings; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.DialogFragmentNetworkTransmitSettings; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.DialogRelayRetransmitSettings; public class ConfigurationServerActivity extends BaseModelConfigurationActivity implements DialogFragmentNetworkTransmitSettings.DialogFragmentNetworkTransmitSettingsListener, @@ -34,17 +35,11 @@ public class ConfigurationServerActivity extends BaseModelConfigurationActivity private static final int NETWORK_TRANSMIT_SETTING_UNKNOWN = -1; private static final int RELAY_RETRANSMIT_SETTINGS_UNKNOWN = -1; - private Button mActionReadRelayState; - private Button mActionSetRelayState; private TextView mRelayRetransmitCountText; private TextView mRelayRetransmitIntervalStepsText; - - private Button mReadNetworkTransmitStateButton; - private Button mSetNetworkTransmitStateButton; private TextView mNetworkTransmitCountText; private TextView mNetworkTransmitIntervalStepsText; - private int mRelay; private int mRelayRetransmitCount = RELAY_RETRANSMIT_SETTINGS_UNKNOWN; private int mRelayRetransmitIntervalSteps = RELAY_RETRANSMIT_SETTINGS_UNKNOWN; private int mNetworkTransmitCount = NETWORK_TRANSMIT_SETTING_UNKNOWN; @@ -56,25 +51,31 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final MeshModel model = mViewModel.getSelectedModel().getValue(); if (model instanceof ConfigurationServerModel) { - //Hide the subscription view since the ConfigurationServerModel is not a subscribe model + //Hide the app key binding, publication adn subscription views since the ConfigurationServerModel does not support app key binding + mContainerAppKeyBinding.setVisibility(View.GONE); + mContainerPublication.setVisibility(View.GONE); mContainerSubscribe.setVisibility(View.GONE); final ConstraintLayout view = findViewById(R.id.node_controls_container); final View nodeControlsContainer = LayoutInflater.from(this) .inflate(R.layout.layout_config_server_model, view); final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); - if(meshNode != null) { - if(meshNode.getNodeFeatures().isRelayFeatureSupported()) { + if (meshNode != null) { + final Button actionReadRelayState = nodeControlsContainer.findViewById(R.id.action_relay_retransmit_get); + mRelayRetransmitIntervalStepsText = nodeControlsContainer.findViewById(R.id.relay_retransmit_interval_steps); + mActionSetRelayState = nodeControlsContainer.findViewById(R.id.action_relay_retransmit_configure); + mRelayRetransmitCountText = nodeControlsContainer.findViewById(R.id.relay_retransmit_count); + if (meshNode.getNodeFeatures().isRelayFeatureSupported()) { final CardView relayCardView = findViewById(R.id.config_relay_set_card); relayCardView.setVisibility(View.VISIBLE); - mRelayRetransmitCountText = nodeControlsContainer.findViewById(R.id.relay_retransmit_count); - mRelayRetransmitIntervalStepsText = nodeControlsContainer.findViewById(R.id.relay_retransmit_interval_steps); - mActionReadRelayState = nodeControlsContainer.findViewById(R.id.action_relay_retransmit_get); - mActionReadRelayState.setOnClickListener(v -> getRelayRetransmit()); + actionReadRelayState.setOnClickListener(v -> { + if (!checkConnectivity()) return; + getRelayRetransmit(); + }); - mActionSetRelayState = nodeControlsContainer.findViewById(R.id.action_relay_retransmit_configure); mActionSetRelayState.setOnClickListener(v -> { + if (!checkConnectivity()) return; final RelaySettings relaySettings = meshNode.getRelaySettings(); if (relaySettings != null) { mRelayRetransmitCount = meshNode.getRelaySettings().getRelayTransmitCount(); @@ -92,11 +93,15 @@ protected void onCreate(Bundle savedInstanceState) { mNetworkTransmitIntervalStepsText = nodeControlsContainer.findViewById(R.id.network_transmit_interval_steps); mReadNetworkTransmitStateButton = nodeControlsContainer.findViewById(R.id.action_network_transmit_get); - mReadNetworkTransmitStateButton.setOnClickListener(v -> getNetworkTransmit()); + mReadNetworkTransmitStateButton.setOnClickListener(v -> { + if (!checkConnectivity()) return; + getNetworkTransmit(); + }); mSetNetworkTransmitStateButton = nodeControlsContainer.findViewById(R.id.action_network_transmit_configure); mSetNetworkTransmitStateButton.setOnClickListener(v -> { - if (meshNode.getNetworkTransmitSettings() != null) { + if (!checkConnectivity()) return; + if (meshNode != null && meshNode.getNetworkTransmitSettings() != null) { mNetworkTransmitCount = meshNode.getNetworkTransmitSettings().getNetworkTransmitCount(); mNetworkTransmitIntervalSteps = meshNode.getNetworkTransmitSettings().getNetworkIntervalSteps(); } @@ -106,8 +111,10 @@ protected void onCreate(Bundle savedInstanceState) { }); mViewModel.getSelectedMeshNode().observe(this, node -> { - updateNetworkTransmitUi(node); - updateRelayUi(node); + if (node != null) { + updateNetworkTransmitUi(node); + updateRelayUi(node); + } }); if (savedInstanceState == null) { @@ -120,25 +127,62 @@ protected void onCreate(Bundle savedInstanceState) { private void getRelayRetransmit() { final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); try { - if(node != null) { + if (node != null) { ConfigRelayGet message = new ConfigRelayGet(); - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), message); - showProgressbar(); + sendMessage(node.getUnicastAddress(), message); } } catch (Exception e) { Log.e(TAG, "Exception while constructing ConfigNetworkTransmitGet", e); } } + @Override + protected void updateMeshMessage(final MeshMessage meshMessage) { + super.updateMeshMessage(meshMessage); + if (meshMessage instanceof ConfigNetworkTransmitStatus) { + final ConfigNetworkTransmitStatus status = (ConfigNetworkTransmitStatus) meshMessage; + final ProvisionedMeshNode meshNode = mViewModel.getNetworkLiveData().getMeshNetwork().getNode(status.getSrc()); + updateNetworkTransmitUi(meshNode); + } else if (meshMessage instanceof ConfigRelayStatus) { + final ConfigRelayStatus status = (ConfigRelayStatus) meshMessage; + final ProvisionedMeshNode meshNode = mViewModel.getNetworkLiveData().getMeshNetwork().getNode(status.getSrc()); + updateRelayUi(meshNode); + } + } + + @Override + protected void enableClickableViews() { + super.enableClickableViews(); + mReadNetworkTransmitStateButton.setEnabled(true); + mSetNetworkTransmitStateButton.setEnabled(true); + } + + @Override + protected void disableClickableViews() { + super.disableClickableViews(); + mReadNetworkTransmitStateButton.setEnabled(false); + mSetNetworkTransmitStateButton.setEnabled(false); + } + + @Override + public void onNetworkTransmitSettingsEntered(int transmitCount, int transmitIntervalSteps) { + setNetworkTransmit(transmitCount, transmitIntervalSteps); + } + + @Override + public void onRelayRetransmitSet(final int relay, final int retransmitCount, final int retransmitIntervalSteps) { + setRelayRetransmit(relay, retransmitCount, retransmitIntervalSteps); + } + private void getNetworkTransmit() { final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); try { - if(node != null) { + if (node != null) { ConfigNetworkTransmitGet message = new ConfigNetworkTransmitGet(); - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), message); - showProgressbar(); + sendMessage(node.getUnicastAddress(), message); } } catch (Exception e) { + hideProgressBar(); Log.e(TAG, "Exception while constructing ConfigNetworkTransmitGet", e); } } @@ -146,12 +190,12 @@ private void getNetworkTransmit() { private void setRelayRetransmit(final int relay, final int relayRetransmit, final int relayRetransmitIntervalSteps) { final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); try { - if(node != null) { + if (node != null) { final ConfigRelaySet message = new ConfigRelaySet(relay, relayRetransmit, relayRetransmitIntervalSteps); - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), message); - showProgressbar(); + sendMessage(node.getUnicastAddress(), message); } } catch (Exception e) { + hideProgressBar(); Log.e(TAG, "Exception while ConfigNetworkTransmitSet: " + e.getMessage()); } } @@ -159,87 +203,46 @@ private void setRelayRetransmit(final int relay, final int relayRetransmit, fina private void setNetworkTransmit(final int networkTransmitCount, final int networkTransmitIntervalSteps) { final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); try { - if(node != null) { + if (node != null) { final ConfigNetworkTransmitSet message = new ConfigNetworkTransmitSet(networkTransmitCount, networkTransmitIntervalSteps); - mViewModel.getMeshManagerApi().sendMeshMessage(node.getUnicastAddress(), message); - showProgressbar(); + sendMessage(node.getUnicastAddress(), message); } } catch (Exception e) { + hideProgressBar(); Log.e(TAG, "Error ConfigNetworkTransmitSet: " + e.getMessage()); } } - @Override - protected void updateMeshMessage(final MeshMessage meshMessage) { - super.updateMeshMessage(meshMessage); - if (meshMessage instanceof ConfigNetworkTransmitStatus) { - final ConfigNetworkTransmitStatus status = (ConfigNetworkTransmitStatus) meshMessage; - final ProvisionedMeshNode meshNode = mViewModel.getMeshManagerApi().getMeshNetwork().getProvisionedNode(status.getSrc()); - updateNetworkTransmitUi(meshNode); - } else if (meshMessage instanceof ConfigRelayStatus) { - final ConfigRelayStatus status = (ConfigRelayStatus) meshMessage; - final ProvisionedMeshNode meshNode = mViewModel.getMeshManagerApi().getMeshNetwork().getProvisionedNode(status.getSrc()); - updateRelayUi(meshNode); - } - } - - private void updateNetworkTransmitUi(final ProvisionedMeshNode meshNode) { - if(meshNode != null) { - final NetworkTransmitSettings networkTransmitSettings = meshNode.getNetworkTransmitSettings(); - if (networkTransmitSettings != null) { - mSetNetworkTransmitStateButton.setEnabled(true); - mNetworkTransmitCountText.setText(getString(R.string.text_network_transmit_count, - networkTransmitSettings.getTransmissionCount())); - mNetworkTransmitIntervalStepsText.setText(getString(R.string.text_network_transmit_interval_steps, - networkTransmitSettings.getNetworkTransmissionInterval())); - } else { - mSetNetworkTransmitStateButton.setEnabled(false); - mNetworkTransmitCountText.setText(getResources().getString(R.string.unknown)); - mNetworkTransmitIntervalStepsText.setText(getResources().getString(R.string.unknown)); - } + private void updateNetworkTransmitUi(@NonNull final ProvisionedMeshNode meshNode) { + final NetworkTransmitSettings networkTransmitSettings = meshNode.getNetworkTransmitSettings(); + if (networkTransmitSettings != null) { + mSetNetworkTransmitStateButton.setEnabled(true); + mNetworkTransmitCountText.setText(getResources().getQuantityString(R.plurals.transmit_count, + networkTransmitSettings.getTransmissionCount(), + networkTransmitSettings.getTransmissionCount())); + mNetworkTransmitIntervalStepsText.setText(getString(R.string.time_ms, + networkTransmitSettings.getNetworkTransmissionInterval())); + } else { + mSetNetworkTransmitStateButton.setEnabled(false); + mNetworkTransmitCountText.setText(getResources().getString(R.string.unknown)); + mNetworkTransmitIntervalStepsText.setText(getResources().getString(R.string.unknown)); } } private void updateRelayUi(@NonNull final ProvisionedMeshNode meshNode) { - //noinspection ConstantConditions - if(meshNode != null) { - final RelaySettings relaySettings = meshNode.getRelaySettings(); - if (relaySettings != null) { - mActionSetRelayState.setEnabled(true); - mRelayRetransmitCountText.setText(getString(R.string.summary_network_transmit_count, - relaySettings.getRelayTransmitCount(), - relaySettings.getTotalTransmissionsCount())); - mRelayRetransmitIntervalStepsText.setText(getString(R.string.text_network_transmit_interval_steps, - relaySettings.getRetransmissionIntervals())); - } else { - mActionSetRelayState.setEnabled(false); - mRelayRetransmitCountText.setText(getResources().getString(R.string.unknown)); - mRelayRetransmitIntervalStepsText.setText(getResources().getString(R.string.unknown)); - } + final RelaySettings relaySettings = meshNode.getRelaySettings(); + if (relaySettings != null) { + mActionSetRelayState.setEnabled(true); + mRelayRetransmitCountText.setText(getResources().getQuantityString(R.plurals.summary_network_transmit_count, + relaySettings.getRelayTransmitCount(), + relaySettings.getRelayTransmitCount(), + relaySettings.getTotalTransmissionsCount())); + mRelayRetransmitIntervalStepsText.setText(getString(R.string.time_ms, + relaySettings.getRetransmissionIntervals())); + } else { + mActionSetRelayState.setEnabled(false); + mRelayRetransmitCountText.setText(getResources().getString(R.string.unknown)); + mRelayRetransmitIntervalStepsText.setText(getResources().getString(R.string.unknown)); } } - - @Override - protected void enableClickableViews() { - super.enableClickableViews(); - mReadNetworkTransmitStateButton.setEnabled(true); - mSetNetworkTransmitStateButton.setEnabled(true); - } - - @Override - protected void disableClickableViews() { - super.disableClickableViews(); - mReadNetworkTransmitStateButton.setEnabled(false); - mSetNetworkTransmitStateButton.setEnabled(false); - } - - @Override - public void onNetworkTransmitSettingsEntered(int transmitCount, int transmitIntervalSteps) { - setNetworkTransmit(transmitCount, transmitIntervalSteps); - } - - @Override - public void onRelayRetransmitSet(final int relay, final int retransmitCount, final int retransmitIntervalSteps) { - setRelayRetransmit(relay, retransmitCount, retransmitIntervalSteps); - } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GenericLevelServerActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/GenericLevelServerActivity.java similarity index 88% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GenericLevelServerActivity.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/GenericLevelServerActivity.java index 92f936f1c..193083546 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GenericLevelServerActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/GenericLevelServerActivity.java @@ -1,17 +1,19 @@ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node; -import android.arch.lifecycle.ViewModelProvider; import android.os.Bundle; -import android.support.constraint.ConstraintLayout; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.SeekBar; import android.widget.TextView; -import android.widget.Toast; + +import com.google.android.material.snackbar.Snackbar; import javax.inject.Inject; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.lifecycle.ViewModelProvider; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; import no.nordicsemi.android.meshprovisioner.models.GenericLevelServerModel; import no.nordicsemi.android.meshprovisioner.transport.Element; import no.nordicsemi.android.meshprovisioner.transport.GenericLevelGet; @@ -20,9 +22,9 @@ import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; import no.nordicsemi.android.meshprovisioner.transport.MeshModel; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.meshprovisioner.utils.CompositionDataParser; import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; public class GenericLevelServerActivity extends BaseModelConfigurationActivity { @@ -61,15 +63,12 @@ protected void onCreate(Bundle savedInstanceState) { final TextView delayTime = nodeControlsContainer.findViewById(R.id.delay_time); level = nodeControlsContainer.findViewById(R.id.level); - mLevelSeekBar = nodeControlsContainer.findViewById(R.id.level_seekbar); + mLevelSeekBar = nodeControlsContainer.findViewById(R.id.level_seek_bar); mLevelSeekBar.setProgress(0); mLevelSeekBar.setMax(100); mActionRead = nodeControlsContainer.findViewById(R.id.action_read); - mActionRead.setOnClickListener(v -> { - sendGenericLevelGet(); - showProgressbar(); - }); + mActionRead.setOnClickListener(v -> sendGenericLevelGet()); mTransitionTimeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { int lastValue = 0; @@ -156,7 +155,6 @@ public void onStartTrackingTouch(final SeekBar seekBar) { @Override public void onStopTrackingTouch(final SeekBar seekBar) { - showProgressbar(); final int level = seekBar.getProgress(); final int delay = mDelaySeekBar.getProgress(); final int genericLevel = ((level * 65535) / 100) - 32768; @@ -212,21 +210,21 @@ protected void updateMeshMessage(final MeshMessage meshMessage) { * Send generic on off get to mesh node */ public void sendGenericLevelGet() { + if (!checkConnectivity()) return; final Element element = mViewModel.getSelectedElement().getValue(); if (element != null) { final MeshModel model = mViewModel.getSelectedModel().getValue(); if (model != null) { if (!model.getBoundAppKeyIndexes().isEmpty()) { final int appKeyIndex = model.getBoundAppKeyIndexes().get(0); - final byte[] appKey = model.getBoundAppKey(appKeyIndex).getKey(); + final ApplicationKey appKey = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(appKeyIndex); final int address = element.getElementAddress(); Log.v(TAG, "Sending message to element's unicast address: " + MeshAddress.formatAddress(address, true)); - final GenericLevelGet genericLevelGet = new GenericLevelGet(appKey); - mViewModel.getMeshManagerApi().sendMeshMessage(address, genericLevelGet); + sendMessage(address, genericLevelGet); } else { - Toast.makeText(this, R.string.error_no_app_keys_bound, Toast.LENGTH_SHORT).show(); + mViewModel.displaySnackBar(this, mContainer, getString(R.string.error_no_app_keys_bound), Snackbar.LENGTH_LONG); } } } @@ -239,6 +237,7 @@ public void sendGenericLevelGet() { * @param delay message execution delay in 5ms steps. After this delay milliseconds the model will execute the required behaviour. */ public void sendGenericLevel(final int level, final Integer delay) { + if (!checkConnectivity()) return; final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); if (node != null) { final Element element = mViewModel.getSelectedElement().getValue(); @@ -247,15 +246,13 @@ public void sendGenericLevel(final int level, final Integer delay) { if (model != null) { if (!model.getBoundAppKeyIndexes().isEmpty()) { final int appKeyIndex = model.getBoundAppKeyIndexes().get(0); - final byte[] appKey = model.getBoundAppKey(appKeyIndex).getKey(); + final ApplicationKey appKey = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(appKeyIndex); final int address = element.getElementAddress(); - Log.v(TAG, "No subscription addresses found for model: " + CompositionDataParser.formatModelIdentifier(model.getModelId(), true) - + ". Sending message to element's unicast address: " + MeshAddress.formatAddress(address, true)); - final GenericLevelSet genericLevelSet = new GenericLevelSet(appKey, mTransitionSteps, mTransitionStepResolution, delay, level, node.getReceivedSequenceNumber()); - mViewModel.getMeshManagerApi().sendMeshMessage(address, genericLevelSet); - showProgressbar(); + final GenericLevelSet genericLevelSet = new GenericLevelSet(appKey, mTransitionSteps, mTransitionStepResolution, delay, level, + node.getSequenceNumber()); + sendMessage(address, genericLevelSet); } else { - Toast.makeText(this, R.string.error_no_app_keys_bound, Toast.LENGTH_SHORT).show(); + mViewModel.displaySnackBar(this, mContainer, getString(R.string.error_no_app_keys_bound), Snackbar.LENGTH_LONG); } } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GenericOnOffServerActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/GenericOnOffServerActivity.java similarity index 87% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GenericOnOffServerActivity.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/GenericOnOffServerActivity.java index b1e2986a9..d2f5095fe 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/GenericOnOffServerActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/GenericOnOffServerActivity.java @@ -1,18 +1,20 @@ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node; -import android.arch.lifecycle.ViewModelProvider; import android.os.Bundle; -import android.support.constraint.ConstraintLayout; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.SeekBar; import android.widget.TextView; -import android.widget.Toast; + +import com.google.android.material.snackbar.Snackbar; import javax.inject.Inject; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.lifecycle.ViewModelProvider; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; import no.nordicsemi.android.meshprovisioner.models.GenericOnOffServerModel; import no.nordicsemi.android.meshprovisioner.transport.Element; import no.nordicsemi.android.meshprovisioner.transport.GenericOnOffGet; @@ -21,9 +23,9 @@ import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; import no.nordicsemi.android.meshprovisioner.transport.MeshModel; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.meshprovisioner.utils.CompositionDataParser; import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; public class GenericOnOffServerActivity extends BaseModelConfigurationActivity { @@ -67,7 +69,7 @@ protected void onCreate(Bundle savedInstanceState) { sendGenericOnOff(false, delaySeekBar.getProgress()); } } catch (IllegalArgumentException ex) { - Toast.makeText(this, ex.getMessage(), Toast.LENGTH_SHORT).show(); + mViewModel.displaySnackBar(this, mContainer, ex.getMessage(), Snackbar.LENGTH_LONG); } }); @@ -200,22 +202,22 @@ protected void updateMeshMessage(final MeshMessage meshMessage) { * Send generic on off get to mesh node */ public void sendGenericOnOffGet() { + if (!checkConnectivity()) return; final Element element = mViewModel.getSelectedElement().getValue(); - if(element != null) { + if (element != null) { final MeshModel model = mViewModel.getSelectedModel().getValue(); if (model != null) { if (!model.getBoundAppKeyIndexes().isEmpty()) { final int appKeyIndex = model.getBoundAppKeyIndexes().get(0); - final byte[] appKey = model.getBoundAppKey(appKeyIndex).getKey(); + final ApplicationKey appKey = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(appKeyIndex); final int address = element.getElementAddress(); Log.v(TAG, "Sending message to element's unicast address: " + MeshAddress.formatAddress(address, true)); final GenericOnOffGet genericOnOffSet = new GenericOnOffGet(appKey); - mViewModel.getMeshManagerApi().sendMeshMessage(address, genericOnOffSet); - showProgressbar(); + sendMessage(address, genericOnOffSet); } else { - Toast.makeText(this, R.string.error_no_app_keys_bound, Toast.LENGTH_SHORT).show(); + mViewModel.displaySnackBar(this, mContainer, getString(R.string.error_no_app_keys_bound), Snackbar.LENGTH_LONG); } } } @@ -228,23 +230,22 @@ public void sendGenericOnOffGet() { * @param delay message execution delay in 5ms steps. After this delay milliseconds the model will execute the required behaviour. */ public void sendGenericOnOff(final boolean state, final Integer delay) { + if (!checkConnectivity()) return; final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); - if(node != null) { + if (node != null) { final Element element = mViewModel.getSelectedElement().getValue(); if (element != null) { final MeshModel model = mViewModel.getSelectedModel().getValue(); if (model != null) { if (!model.getBoundAppKeyIndexes().isEmpty()) { final int appKeyIndex = model.getBoundAppKeyIndexes().get(0); - final byte[] appKey = model.getBoundAppKey(appKeyIndex).getKey(); + final ApplicationKey appKey = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(appKeyIndex); final int address = element.getElementAddress(); - Log.v(TAG, "No subscription addresses found for model: " + CompositionDataParser.formatModelIdentifier(model.getModelId(), true) - + ". Sending message to element's unicast address: " + MeshAddress.formatAddress(address, true)); - final GenericOnOffSet genericOnOffSet = new GenericOnOffSet(appKey, state, node.getReceivedSequenceNumber(), mTransitionSteps, mTransitionStepResolution, delay); - mViewModel.getMeshManagerApi().sendMeshMessage(address, genericOnOffSet); - showProgressbar(); + final GenericOnOffSet genericOnOffSet = new GenericOnOffSet(appKey, state, + node.getSequenceNumber(), mTransitionSteps, mTransitionStepResolution, delay); + sendMessage(address, genericOnOffSet); } else { - Toast.makeText(this, R.string.error_no_app_keys_bound, Toast.LENGTH_SHORT).show(); + mViewModel.displaySnackBar(this, mContainer, getString(R.string.error_no_app_keys_bound), Snackbar.LENGTH_LONG); } } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ModelConfigurationActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/ModelConfigurationActivity.java similarity index 80% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ModelConfigurationActivity.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/ModelConfigurationActivity.java index 352011592..4e1ae8549 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/ModelConfigurationActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/ModelConfigurationActivity.java @@ -1,4 +1,4 @@ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node; import android.os.Bundle; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/NodeConfigurationActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/NodeConfigurationActivity.java new file mode 100644 index 000000000..e5b7839a9 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/NodeConfigurationActivity.java @@ -0,0 +1,543 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.node; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.models.SigModelParser; +import no.nordicsemi.android.meshprovisioner.transport.ConfigCompositionDataGet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigCompositionDataStatus; +import no.nordicsemi.android.meshprovisioner.transport.ConfigDefaultTtlGet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigDefaultTtlSet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigDefaultTtlStatus; +import no.nordicsemi.android.meshprovisioner.transport.ConfigNodeReset; +import no.nordicsemi.android.meshprovisioner.transport.ConfigNodeResetStatus; +import no.nordicsemi.android.meshprovisioner.transport.ConfigProxyGet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigProxyStatus; +import no.nordicsemi.android.meshprovisioner.transport.Element; +import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; +import no.nordicsemi.android.meshprovisioner.transport.MeshModel; +import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigFilterStatus; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentConfigurationComplete; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentError; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentProxySet; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentTransactionStatus; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AddAppKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AddNetKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.adapter.ElementAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.DialogFragmentElementName; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.DialogFragmentNodeName; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.DialogFragmentResetNode; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentTtl; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NodeConfigurationViewModel; + +public class NodeConfigurationActivity extends AppCompatActivity implements Injectable, + DialogFragmentNodeName.DialogFragmentNodeNameListener, + DialogFragmentElementName.DialogFragmentElementNameListener, + DialogFragmentTtl.DialogFragmentTtlListener, + ElementAdapter.OnItemClickListener, + DialogFragmentResetNode.DialogFragmentNodeResetListener, + DialogFragmentConfigurationComplete.ConfigurationCompleteListener { + + private static final String PROGRESS_BAR_STATE = "PROGRESS_BAR_STATE"; + private static final String PROXY_STATE = "PROXY_STATE"; + private static final String REQUESTED_PROXY_STATE = "REQUESTED_PROXY_STATE"; + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + @BindView(R.id.container) + CoordinatorLayout mContainer; + @BindView(R.id.action_get_composition_data) + Button actionGetCompositionData; + @BindView(R.id.node_proxy_state_card) + View mProxyStateCard; + @BindView(R.id.proxy_state_summary) + TextView mProxyStateRationaleSummary; + @BindView(R.id.action_get_default_ttl) + Button actionGetDefaultTtl; + @BindView(R.id.action_set_default_ttl) + Button actionSetDefaultTtl; + @BindView(R.id.action_get_proxy_state) + Button actionGetProxyState; + @BindView(R.id.action_set_proxy_state) + Button actionSetProxyState; + @BindView(R.id.action_reset_node) + Button actionResetNode; + @BindView(R.id.recycler_view_elements) + RecyclerView mRecyclerViewElements; + @BindView(R.id.configuration_progress_bar) + ProgressBar mProgressbar; + + private NodeConfigurationViewModel mViewModel; + private Handler mHandler; + private boolean mProxyState; + private boolean mRequestedState = true; + private boolean mIsConnected; + + private final Runnable mRunnableOperationTimeout = () -> { + hideProgressBar(); + if (mViewModel.isActivityVisibile()) { + DialogFragmentTransactionStatus fragmentMessage = DialogFragmentTransactionStatus. + newInstance(getString(R.string.title_transaction_failed), getString(R.string.operation_timed_out)); + fragmentMessage.show(getSupportFragmentManager(), null); + } + }; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_node_configuration); + ButterKnife.bind(this); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(NodeConfigurationViewModel.class); + + if (savedInstanceState != null) { + if (savedInstanceState.getBoolean(PROGRESS_BAR_STATE)) { + mProgressbar.setVisibility(View.VISIBLE); + disableClickableViews(); + } else { + mProgressbar.setVisibility(View.INVISIBLE); + enableClickableViews(); + } + mRequestedState = savedInstanceState.getBoolean(PROXY_STATE, true); + mProxyState = savedInstanceState.getBoolean(PROXY_STATE, true); + } + + mHandler = new Handler(); + if (mViewModel.getSelectedMeshNode().getValue() == null) { + finish(); + } + // Set up views + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(R.string.title_node_configuration); + + final View containerNodeName = findViewById(R.id.container_node_name); + containerNodeName.findViewById(R.id.image) + .setBackground(ContextCompat.getDrawable(this, R.drawable.ic_label_black_alpha_24dp)); + final TextView nodeNameTitle = containerNodeName.findViewById(R.id.title); + nodeNameTitle.setText(R.string.title_node_name); + final TextView nodeNameView = containerNodeName.findViewById(R.id.text); + nodeNameView.setVisibility(View.VISIBLE); + containerNodeName.setOnClickListener(v -> { + final DialogFragmentNodeName fragment = DialogFragmentNodeName. + newInstance(nodeNameView.getText().toString()); + fragment.show(getSupportFragmentManager(), null); + }); + final Button actionDetails = findViewById(R.id.action_show_details); + actionDetails.setOnClickListener(v -> { + final Intent intent = new Intent(NodeConfigurationActivity.this, NodeDetailsActivity.class); + startActivity(intent); + }); + + final TextView noElementsFound = findViewById(R.id.no_elements); + final View compositionActionContainer = findViewById(R.id.composition_action_container); + mRecyclerViewElements.setLayoutManager(new LinearLayoutManager(this)); + final ElementAdapter adapter = new ElementAdapter(this, mViewModel.getSelectedMeshNode()); + adapter.setHasStableIds(true); + adapter.setOnItemClickListener(this); + mRecyclerViewElements.setAdapter(adapter); + + final View containerNetKey = findViewById(R.id.container_net_keys); + containerNetKey.findViewById(R.id.image) + .setBackground(ContextCompat.getDrawable(this, R.drawable.ic_vpn_key_black_alpha_24dp)); + final TextView keyTitle = containerNetKey.findViewById(R.id.title); + keyTitle.setText(R.string.title_net_keys); + final TextView netKeySummary = containerNetKey.findViewById(R.id.text); + netKeySummary.setVisibility(View.VISIBLE); + containerNetKey.setOnClickListener(v -> { + final Intent intent = new Intent(this, AddNetKeysActivity.class); + startActivity(intent); + }); + + final View containerAppKey = findViewById(R.id.container_app_keys); + containerAppKey.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_vpn_key_black_alpha_24dp)); + ((TextView) containerAppKey.findViewById(R.id.title)).setText(R.string.title_app_keys); + final TextView appKeySummary = containerAppKey.findViewById(R.id.text); + appKeySummary.setVisibility(View.VISIBLE); + containerAppKey.setOnClickListener(v -> { + final Intent intent = new Intent(this, AddAppKeysActivity.class); + startActivity(intent); + }); + + final View containerDefaultTtl = findViewById(R.id.container_ttl); + containerDefaultTtl.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_numeric)); + ((TextView) containerDefaultTtl.findViewById(R.id.title)).setText(R.string.title_ttl); + final TextView defaultTtlSummary = containerDefaultTtl.findViewById(R.id.text); + defaultTtlSummary.setVisibility(View.VISIBLE); + + mViewModel.getSelectedMeshNode().observe(this, meshNode -> { + if (meshNode == null) { + finish(); + return; + } + getSupportActionBar().setSubtitle(meshNode.getNodeName()); + nodeNameView.setText(meshNode.getNodeName()); + + updateClickableViews(); + + if (!meshNode.getElements().isEmpty()) { + compositionActionContainer.setVisibility(View.GONE); + noElementsFound.setVisibility(View.INVISIBLE); + mRecyclerViewElements.setVisibility(View.VISIBLE); + } else { + noElementsFound.setVisibility(View.VISIBLE); + compositionActionContainer.setVisibility(View.VISIBLE); + mRecyclerViewElements.setVisibility(View.INVISIBLE); + } + + if (!meshNode.getAddedNetKeys().isEmpty()) { + netKeySummary.setText(String.valueOf(meshNode.getAddedNetKeys().size())); + } else { + netKeySummary.setText(R.string.no_app_keys_added); + } + + if (!meshNode.getAddedAppKeys().isEmpty()) { + appKeySummary.setText(String.valueOf(meshNode.getAddedAppKeys().size())); + } else { + appKeySummary.setText(R.string.no_app_keys_added); + } + + if (meshNode.getTtl() != null) { + defaultTtlSummary.setText(String.valueOf(meshNode.getTtl())); + } else { + defaultTtlSummary.setText(R.string.unknown); + } + }); + + actionGetCompositionData.setOnClickListener(v -> { + if (!checkConnectivity()) return; + final ConfigCompositionDataGet configCompositionDataGet = new ConfigCompositionDataGet(); + sendMessage(configCompositionDataGet); + }); + + actionGetDefaultTtl.setOnClickListener(v -> { + if (!checkConnectivity()) return; + final ConfigDefaultTtlGet defaultTtlGet = new ConfigDefaultTtlGet(); + sendMessage(defaultTtlGet); + }); + + actionSetDefaultTtl.setOnClickListener(v -> { + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + if (node != null) { + DialogFragmentTtl fragmentTtl = DialogFragmentTtl.newInstance(node.getTtl() == null ? -1 : node.getTtl()); + fragmentTtl.show(getSupportFragmentManager(), null); + } + }); + + actionGetProxyState.setOnClickListener(v -> { + if (!checkConnectivity()) return; + final ConfigProxyGet configProxyGet = new ConfigProxyGet(); + sendMessage(configProxyGet); + }); + + actionSetProxyState.setOnClickListener(v -> { + final String message; + if (mProxyState) { + message = getString(R.string.proxy_set_off_rationale_summary); + } else { + message = getString(R.string.proxy_set_on_rationale_summary); + } + final DialogFragmentProxySet resetNodeFragment = DialogFragmentProxySet. + newInstance(getString(R.string.title_proxy_state_settings), message, !mProxyState); + resetNodeFragment.show(getSupportFragmentManager(), null); + }); + + actionResetNode.setOnClickListener(v -> { + if (!checkConnectivity()) return; + final DialogFragmentResetNode resetNodeFragment = DialogFragmentResetNode. + newInstance(getString(R.string.title_reset_node), getString(R.string.reset_node_rationale_summary)); + resetNodeFragment.show(getSupportFragmentManager(), null); + }); + + mViewModel.getTransactionStatus().observe(this, transactionStatus -> { + if (transactionStatus != null) { + hideProgressBar(); + final String message; + if (transactionStatus.isIncompleteTimerExpired()) { + message = getString(R.string.segments_not_received_timed_out); + } else { + message = getString(R.string.operation_timed_out); + } + DialogFragmentTransactionStatus fragmentMessage = DialogFragmentTransactionStatus.newInstance(getString(R.string.title_transaction_failed), message); + fragmentMessage.show(getSupportFragmentManager(), null); + } + }); + + mViewModel.isConnectedToProxy().observe(this, isConnected -> { + if (isConnected != null) { + mIsConnected = isConnected; + hideProgressBar(); + } + updateClickableViews(); + invalidateOptionsMenu(); + }); + + mViewModel.getMeshMessage().observe(this, this::updateMeshMessage); + + updateProxySettingsCardUi(); + + final Boolean isConnectedToNetwork = mViewModel.isConnectedToProxy().getValue(); + if (isConnectedToNetwork != null) { + mIsConnected = isConnectedToNetwork; + } + invalidateOptionsMenu(); + } + + @Override + protected void onStart() { + super.onStart(); + mViewModel.setActivityVisible(true); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + if (mIsConnected) { + getMenuInflater().inflate(R.menu.disconnect, menu); + } else { + getMenuInflater().inflate(R.menu.connect, menu); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + case R.id.action_connect: + mViewModel.navigateToScannerActivity(this, false, Utils.CONNECT_TO_NETWORK, false); + return true; + case R.id.action_disconnect: + mViewModel.disconnect(); + return true; + default: + return false; + } + } + + @Override + protected void onStop() { + super.onStop(); + mViewModel.setActivityVisible(false); + if (isFinishing()) { + mHandler.removeCallbacksAndMessages(null); + } + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + } + + @Override + protected void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(PROGRESS_BAR_STATE, mProgressbar.getVisibility() == View.VISIBLE); + outState.putBoolean(PROXY_STATE, mProxyState); + outState.putBoolean(REQUESTED_PROXY_STATE, mRequestedState); + } + + @Override + public void onElementClicked(@NonNull final Element element) { + final DialogFragmentElementName fragmentElementName = DialogFragmentElementName.newInstance(element); + fragmentElementName.show(getSupportFragmentManager(), null); + } + + @Override + public void onModelClicked(@NonNull final ProvisionedMeshNode meshNode, @NonNull final Element element, @NonNull final MeshModel model) { + mViewModel.setSelectedElement(element); + mViewModel.setSelectedModel(model); + mViewModel.navigateToModelActivity(this, model); + } + + @Override + public void onNodeReset() { + final ConfigNodeReset configNodeReset = new ConfigNodeReset(); + sendMessage(configNodeReset); + } + + @Override + public void onConfigurationCompleted() { + //Do nothing + } + + @Override + public boolean onNodeNameUpdated(@NonNull final String nodeName) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + if (node != null) { + node.setNodeName(nodeName); + return network.updateNodeName(node, nodeName); + } + return false; + } + + @Override + public boolean onElementNameUpdated(@NonNull final Element element, @NonNull final String name) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + network.updateElementName(element, name); + } + + return true; + } + + private void updateProxySettingsCardUi() { + final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); + if (meshNode != null && meshNode.getNodeFeatures() != null && meshNode.getNodeFeatures().isProxyFeatureSupported()) { + mProxyStateCard.setVisibility(View.VISIBLE); + updateProxySettingsButtonUi(); + } + } + + private void updateProxySettingsButtonUi() { + if (mProxyState) { + mProxyStateRationaleSummary.setText(R.string.proxy_set_off_rationale); + actionSetProxyState.setText(R.string.action_proxy_state_set_off); + } else { + mProxyStateRationaleSummary.setText(R.string.proxy_set_on_rationale); + actionSetProxyState.setText(R.string.action_proxy_state_set_on); + } + } + + private void showProgressbar() { + mHandler.postDelayed(mRunnableOperationTimeout, Utils.MESSAGE_TIME_OUT); + disableClickableViews(); + mProgressbar.setVisibility(View.VISIBLE); + } + + private void hideProgressBar() { + enableClickableViews(); + mProgressbar.setVisibility(View.INVISIBLE); + mHandler.removeCallbacks(mRunnableOperationTimeout); + } + + private void enableClickableViews() { + actionGetCompositionData.setEnabled(true); + actionGetDefaultTtl.setEnabled(true); + actionSetDefaultTtl.setEnabled(true); + actionGetProxyState.setEnabled(true); + actionSetProxyState.setEnabled(true); + actionResetNode.setEnabled(true); + } + + private void disableClickableViews() { + actionGetCompositionData.setEnabled(false); + actionGetDefaultTtl.setEnabled(false); + actionSetDefaultTtl.setEnabled(false); + actionGetProxyState.setEnabled(false); + actionSetProxyState.setEnabled(false); + actionResetNode.setEnabled(false); + } + + private void updateMeshMessage(final MeshMessage meshMessage) { + if (meshMessage instanceof ProxyConfigFilterStatus) { + hideProgressBar(); + } + if (meshMessage instanceof ConfigCompositionDataStatus) { + hideProgressBar(); + } else if (meshMessage instanceof ConfigDefaultTtlStatus) { + hideProgressBar(); + } else if (meshMessage instanceof ConfigNodeResetStatus) { + hideProgressBar(); + finish(); + } else if (meshMessage instanceof ConfigProxyStatus) { + final ConfigProxyStatus status = (ConfigProxyStatus) meshMessage; + mProxyState = status.isProxyFeatureEnabled(); + updateProxySettingsCardUi(); + hideProgressBar(); + } + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + protected final boolean checkConnectivity() { + if (!mIsConnected) { + mViewModel.displayDisconnectedSnackBar(this, mContainer); + return false; + } + return true; + } + + private void updateClickableViews() { + final ProvisionedMeshNode meshNode = mViewModel.getSelectedMeshNode().getValue(); + if (meshNode != null && meshNode.isConfigured() && + !mViewModel.isModelExists(SigModelParser.CONFIGURATION_SERVER)) + disableClickableViews(); + } + + private void sendMessage(final MeshMessage meshMessage) { + try { + if (!checkConnectivity()) + return; + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + if (node != null) { + mViewModel.getMeshManagerApi().createMeshPdu(node.getUnicastAddress(), meshMessage); + showProgressbar(); + } + } catch (IllegalArgumentException ex) { + hideProgressBar(); + final DialogFragmentError message = DialogFragmentError. + newInstance(getString(R.string.title_error), ex.getMessage()); + message.show(getSupportFragmentManager(), null); + } + } + + @Override + public boolean setDefaultTtl(final int ttl) { + final ConfigDefaultTtlSet ttlSet = new ConfigDefaultTtlSet(ttl); + sendMessage(ttlSet); + return true; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NodeDetailsActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/NodeDetailsActivity.java similarity index 72% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NodeDetailsActivity.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/NodeDetailsActivity.java index d5bd521d9..10f8d78b3 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/NodeDetailsActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/NodeDetailsActivity.java @@ -20,40 +20,40 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; -import android.content.Intent; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; import java.text.DateFormat; -import java.util.ArrayList; +import javax.inject.Inject; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.Features; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.meshprovisioner.utils.AddressUtils; import no.nordicsemi.android.meshprovisioner.utils.CompanyIdentifiers; import no.nordicsemi.android.meshprovisioner.utils.CompositionDataParser; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.ElementAdapterDetails; +import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; -import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.NodeDetailsViewModel; -public class NodeDetailsActivity extends AppCompatActivity implements Injectable, ElementAdapterDetails.OnItemClickListener { +public class NodeDetailsActivity extends AppCompatActivity implements Injectable { - private final static String TAG = NodeDetailsActivity.class.getSimpleName(); - private RecyclerView mRecyclerView; + @Inject + ViewModelProvider.Factory mViewModelFactory; @Override protected void onCreate(final Bundle savedInstanceState) { @@ -61,11 +61,12 @@ protected void onCreate(final Bundle savedInstanceState) { setContentView(R.layout.activity_node_details); ButterKnife.bind(this); - final Intent intent = getIntent(); - final ProvisionedMeshNode node = intent.getParcelableExtra(Utils.EXTRA_DEVICE); - if(node == null) + final NodeDetailsViewModel viewModel = ViewModelProviders.of(this, mViewModelFactory).get(NodeDetailsViewModel.class); + if (viewModel.getSelectedMeshNode().getValue() == null) { finish(); + } + final ProvisionedMeshNode node = viewModel.getSelectedMeshNode().getValue(); final ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); final Toolbar toolbar = findViewById(R.id.toolbar); @@ -73,11 +74,6 @@ protected void onCreate(final Bundle savedInstanceState) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(node.getNodeName()); - final View containerNodeName = findViewById(R.id.container_element_count); - containerNodeName.setClickable(false); - final TextView nodeName = containerNodeName.findViewById(R.id.text); - nodeName.setText(node.getNodeName()); - final View containerProvisioningTimeStamp = findViewById(R.id.container_timestamp); containerProvisioningTimeStamp.setClickable(false); final TextView timestamp = containerProvisioningTimeStamp.findViewById(R.id.text); @@ -87,7 +83,7 @@ protected void onCreate(final Bundle savedInstanceState) { final View containerUnicastAddress = findViewById(R.id.container_supported_algorithm); containerUnicastAddress.setClickable(false); final TextView unicastAddress = containerUnicastAddress.findViewById(R.id.text); - unicastAddress.setText(MeshParserUtils.bytesToHex(AddressUtils.getUnicastAddressBytes(node.getUnicastAddress()), false)); + unicastAddress.setText(MeshParserUtils.bytesToHex(MeshAddress.addressIntToBytes(node.getUnicastAddress()), false)); final View containerDeviceKey = findViewById(R.id.container_device_key); containerDeviceKey.setClickable(false); @@ -96,7 +92,7 @@ protected void onCreate(final Bundle savedInstanceState) { final View copyDeviceKey = findViewById(R.id.copy); copyDeviceKey.setOnClickListener(v -> { - if(clipboard != null) { + if (clipboard != null) { final ClipData clipDeviceKey = ClipData.newPlainText("Device Key", MeshParserUtils.bytesToHex(node.getDeviceKey(), false)); clipboard.setPrimaryClip(clipDeviceKey); Toast.makeText(NodeDetailsActivity.this, R.string.device_key_clipboard_copied, Toast.LENGTH_SHORT).show(); @@ -106,16 +102,16 @@ protected void onCreate(final Bundle savedInstanceState) { final View containerCompanyIdentifier = findViewById(R.id.container_company_identifier); containerCompanyIdentifier.setClickable(false); final TextView companyIdentifier = containerCompanyIdentifier.findViewById(R.id.text); - if(node.getCompanyIdentifier() != null) { + if (node.getCompanyIdentifier() != null) { companyIdentifier.setText(CompanyIdentifiers.getCompanyName(node.getCompanyIdentifier().shortValue())); } else { - companyIdentifier.setText(R.string.unavailable); + companyIdentifier.setText(R.string.unknown); } final View containerProductIdentifier = findViewById(R.id.container_product_identifier); containerProductIdentifier.setClickable(false); final TextView productIdentifier = containerProductIdentifier.findViewById(R.id.text); - if(node.getProductIdentifier() != null) { + if (node.getProductIdentifier() != null) { productIdentifier.setText(CompositionDataParser.formatProductIdentifier(node.getProductIdentifier().shortValue(), false)); } else { productIdentifier.setText(R.string.unavailable); @@ -124,7 +120,7 @@ protected void onCreate(final Bundle savedInstanceState) { final View containerProductVersion = findViewById(R.id.container_product_version); containerProductVersion.setClickable(false); final TextView productVersion = containerProductVersion.findViewById(R.id.text); - if(node.getVersionIdentifier() != null) { + if (node.getVersionIdentifier() != null) { productVersion.setText(CompositionDataParser.formatVersionIdentifier(node.getVersionIdentifier().shortValue(), false)); } else { productVersion.setText(R.string.unavailable); @@ -133,7 +129,7 @@ protected void onCreate(final Bundle savedInstanceState) { final View containerCrpl = findViewById(R.id.container_crpl); containerCrpl.setClickable(false); final TextView crpl = containerCrpl.findViewById(R.id.text); - if(node.getCrpl() != null) { + if (node.getCrpl() != null) { crpl.setText(CompositionDataParser.formatReplayProtectionCount(node.getCrpl().shortValue(), false)); } else { crpl.setText(R.string.unavailable); @@ -142,34 +138,18 @@ protected void onCreate(final Bundle savedInstanceState) { final View containerFeatures = findViewById(R.id.container_features); containerFeatures.setClickable(false); final TextView features = containerFeatures.findViewById(R.id.text); - if(node.getNodeFeatures() != null) { + if (node.getNodeFeatures() != null) { features.setText(parseFeatures(node.getNodeFeatures())); } else { features.setText(R.string.unavailable); } - - final TextView view = findViewById(R.id.no_elements_view); - mRecyclerView = findViewById(R.id.recycler_view_elements); - if(node.getElements().isEmpty()){ - view.setVisibility(View.VISIBLE); - mRecyclerView.setVisibility(View.INVISIBLE); - } else { - view.setVisibility(View.GONE); - mRecyclerView.setVisibility(View.VISIBLE); - final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); - mRecyclerView.setLayoutManager(linearLayoutManager); - final ElementAdapterDetails adapter = new ElementAdapterDetails(this, new ArrayList<>(node.getElements().values())); - adapter.setOnItemClickListener(this); - mRecyclerView.setAdapter(adapter); - } } @Override public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; } return false; } @@ -189,24 +169,19 @@ public void onBackPressed() { super.onBackPressed(); } - @Override - public void onItemClick(final int position) { - mRecyclerView.scrollToPosition(position); - } - /** * Returns a String representation of the features */ - private String parseFeatures(final Features features){ + private String parseFeatures(final Features features) { return "Friend feature " + parseFeature(features.isFriendFeatureSupported()) + ", " + "Low power feature " + parseFeature(features.isLowPowerFeatureSupported()) + ", " + "Proxy feature " + parseFeature(features.isProxyFeatureSupported()) + ", " + "Relay feature " + parseFeature(features.isRelayFeatureSupported()); } - public String parseFeature(final boolean isSupported){ - if(isSupported){ + public String parseFeature(final boolean isSupported) { + if (isSupported) { return "supported"; } else { return "unsupported"; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/PublicationSettingsActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/PublicationSettingsActivity.java new file mode 100644 index 000000000..adb365c74 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/PublicationSettingsActivity.java @@ -0,0 +1,557 @@ +package no.nordicsemi.android.nrfmeshprovisioner.node; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ScrollView; +import android.widget.SeekBar; +import android.widget.Switch; +import android.widget.TextView; + +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.Group; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelPublicationSet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigModelPublicationVirtualAddressSet; +import no.nordicsemi.android.meshprovisioner.transport.Element; +import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; +import no.nordicsemi.android.meshprovisioner.transport.MeshModel; +import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import no.nordicsemi.android.meshprovisioner.transport.PublicationSettings; +import no.nordicsemi.android.meshprovisioner.utils.AddressType; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.GroupCallbacks; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentError; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.DialogFragmentPublishAddress; +import no.nordicsemi.android.nrfmeshprovisioner.node.dialog.DialogFragmentPublishTtl; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.PublicationViewModel; + +public class PublicationSettingsActivity extends AppCompatActivity implements Injectable, + GroupCallbacks, + DialogFragmentPublishAddress.DialogFragmentPublicationListener, + DialogFragmentPublishTtl.DialogFragmentPublishTtlListener { + + public static final int SET_PUBLICATION_SETTINGS = 2021; + public static final String RESULT_LABEL_UUID = "RESULT_LABEL_UUID"; + public static final String RESULT_PUBLISH_ADDRESS = "RESULT_PUBLISH_ADDRESS"; + public static final String RESULT_APP_KEY_INDEX = "RESULT_APP_KEY_INDEX"; + public static final String RESULT_CREDENTIAL_FLAG = "RESULT_CREDENTIAL_FLAG"; + public static final String RESULT_PUBLISH_TTL = "RESULT_PUBLISH_TTL"; + public static final String RESULT_PUBLICATION_STEPS = "RESULT_PUBLICATION_STEPS"; + public static final String RESULT_PUBLICATION_RESOLUTION = "RESULT_PUBLICATION_RESOLUTION"; + public static final String RESULT_PUBLISH_RETRANSMIT_COUNT = "RESULT_PUBLISH_RETRANSMIT_COUNT"; + public static final String RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS = "RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS"; + + private static final int DEFAULT_PUB_RETRANSMIT_COUNT = 1; + private static final int DEFAULT_PUB_RETRANSMIT_INTERVAL_STEPS = 1; + private static final int DEFAULT_PUBLICATION_STEPS = 0; + @SuppressWarnings("unused") + private static final int DEFAULT_PUBLICATION_RESOLUTION = MeshParserUtils.RESOLUTION_100_MS; + + private PublicationViewModel mViewModel; + private MeshModel mMeshModel; + private UUID mLabelUUID; + private int mPublishAddress; + private Integer mAppKeyIndex; + private int mPublishTtl = MeshParserUtils.USE_DEFAULT_TTL; + private int mPublicationSteps = DEFAULT_PUBLICATION_STEPS; + private int mPublicationResolution; + private int mRetransmitCount = DEFAULT_PUB_RETRANSMIT_COUNT; + private int mRetransmitIntervalSteps = DEFAULT_PUB_RETRANSMIT_INTERVAL_STEPS; + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + @BindView(R.id.container) + CoordinatorLayout mContainer; + @BindView(R.id.publish_address) + TextView mPublishAddressView; + @BindView(R.id.retransmit_count) + TextView mRetransmitCountView; + @BindView(R.id.retransmit_interval) + TextView mRetransmitInterval; + @BindView(R.id.app_key) + TextView mAppKeyView; + @BindView(R.id.publication_ttl) + TextView mPublishTtlView; + @BindView(R.id.friendship_credential_flag) + Switch mActionFriendshipCredentialSwitch; + @BindView(R.id.retransmission_seek_bar) + SeekBar mRetransmissionCountSeekBar; + @BindView(R.id.interval_steps_seek_bar) + SeekBar mRetransmitIntervalSeekBar; + @BindView(R.id.publish_interval_seek_bar) + SeekBar mPublicationIntervalSeekBar; + @BindView(R.id.pub_interval) + TextView mPublicationInterval; + @BindView(R.id.fab_apply) + ExtendedFloatingActionButton fabApply; + + private boolean mIsConnected; + + @SuppressWarnings("ConstantConditions") + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_publication_settings); + ButterKnife.bind(this); + + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(PublicationViewModel.class); + + final MeshModel meshModel = mMeshModel = mViewModel.getSelectedModel().getValue(); + if (meshModel == null) + finish(); + + //Setup views + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp); + getSupportActionBar().setTitle(R.string.title_publication_settings); + + final ScrollView scrollView = findViewById(R.id.scroll_view); + final View actionPublishAddress = findViewById(R.id.container_publish_address); + final View actionKeyIndex = findViewById(R.id.container_app_key_index); + final View actionPublishTtl = findViewById(R.id.container_publication_ttl); + + scrollView.getViewTreeObserver().addOnScrollChangedListener(() -> { + if (scrollView.getScrollY() == 0) { + fabApply.extend(true); + } else { + fabApply.shrink(true); + } + }); + + actionPublishAddress.setOnClickListener(v -> { + List groups = mViewModel.getNetworkLiveData().getMeshNetwork().getGroups(); + final DialogFragmentPublishAddress fragmentPublishAddress = DialogFragmentPublishAddress. + newInstance(meshModel.getPublicationSettings(), new ArrayList<>(groups)); + fragmentPublishAddress.show(getSupportFragmentManager(), null); + }); + + actionKeyIndex.setOnClickListener(v -> { + final Intent bindAppKeysIntent = new Intent(this, AppKeysActivity.class); + bindAppKeysIntent.putExtra(Utils.EXTRA_DATA, Utils.PUBLICATION_APP_KEY); + startActivityForResult(bindAppKeysIntent, Utils.SELECT_KEY); + }); + + actionPublishTtl.setOnClickListener(v -> { + if (meshModel != null) { + if (meshModel.getPublicationSettings() != null) { + final DialogFragmentPublishTtl fragmentPublishTtl = DialogFragmentPublishTtl + .newInstance(meshModel.getPublicationSettings().getPublishTtl()); + fragmentPublishTtl.show(getSupportFragmentManager(), null); + } else { + final DialogFragmentPublishTtl fragmentPublishTtl = DialogFragmentPublishTtl + .newInstance(MeshParserUtils.USE_DEFAULT_TTL); + fragmentPublishTtl.show(getSupportFragmentManager(), null); + } + } + }); + + mPublicationIntervalSeekBar.setMax(234); + mPublicationIntervalSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + int lastValue = 0; + + @Override + public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { + if (progress == 0) { + lastValue = progress; + mPublicationSteps = progress; + mPublicationResolution = 0; + mPublicationInterval.setText(R.string.disabled); + } else if (progress >= 1 && progress <= 63) { + lastValue = progress; + mPublicationResolution = 0; + mPublicationSteps = progress; + mPublicationInterval.setText(getString(R.string.time_ms, PublicationSettings.getPublishPeriod(mPublicationResolution, mPublicationSteps))); + } else if (progress >= 64 && progress <= 120) { + if (progress > lastValue) { + mPublicationSteps = progress - 57; + lastValue = progress; + } else if (progress < lastValue) { + mPublicationSteps = -(57 - progress); + } + mPublicationResolution = 1; + mPublicationInterval.setText(getString(R.string.time_s, PublicationSettings.getPublishPeriod(mPublicationResolution, mPublicationSteps))); + } else if (progress >= 121 && progress <= 177) { + if (progress > lastValue) { + mPublicationSteps = progress - 114; + lastValue = progress; + } else if (progress < lastValue) { + mPublicationSteps = -(114 - progress); + } + mPublicationResolution = 2; + mPublicationInterval.setText(getString(R.string.time_s, PublicationSettings.getPublishPeriod(mPublicationResolution, mPublicationSteps))); + } else if (progress >= 178 && progress <= 234) { + if (progress >= lastValue) { + mPublicationSteps = progress - 171; + lastValue = progress; + } else { + mPublicationSteps = -(171 - progress); + } + mPublicationResolution = 3; + mPublicationInterval.setText(getString(R.string.time_m, PublicationSettings.getPublishPeriod(mPublicationResolution, mPublicationSteps))); + } + } + + @Override + public void onStartTrackingTouch(final SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(final SeekBar seekBar) { + + } + }); + + mRetransmissionCountSeekBar.setMax(PublicationSettings.MAX_PUBLICATION_RETRANSMIT_COUNT); + mRetransmissionCountSeekBar.incrementProgressBy(1); + mRetransmissionCountSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { + if (fromUser) { + mRetransmitCount = progress; + } + if (progress == 0) { + mRetransmitCountView.setText(R.string.disabled); + mRetransmitInterval.setText(R.string.disabled); + mRetransmitIntervalSeekBar.setEnabled(false); + } else { + if (!mRetransmitIntervalSeekBar.isEnabled()) + mRetransmitIntervalSeekBar.setEnabled(true); + mRetransmitInterval.setText(getString(R.string.time_ms, PublicationSettings. + parseRetransmitIntervalSteps(mRetransmitIntervalSeekBar.getProgress()))); + mRetransmitCountView.setText(getResources().getQuantityString(R.plurals.retransmit_count, + progress, mRetransmitCount)); + } + } + + @Override + public void onStartTrackingTouch(final SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(final SeekBar seekBar) { + } + }); + + mRetransmitIntervalSeekBar.setMax(PublicationSettings.getMaxRetransmissionInterval()); + mRetransmitIntervalSeekBar.incrementProgressBy(1); + mRetransmitIntervalSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { + mRetransmitInterval.setText(getString(R.string.time_ms, progress)); + if (fromUser) { + mRetransmitIntervalSteps = PublicationSettings.parseRetransmitIntervalSteps(progress); + } + } + + @Override + public void onStartTrackingTouch(final SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(final SeekBar seekBar) { + } + }); + + fabApply.setOnClickListener(v -> { + if (!checkConnectivity()) return; + setPublication(); + }); + + mViewModel.isConnectedToProxy().observe(this, isConnected -> { + if (isConnected != null) { + mIsConnected = isConnected; + } + invalidateOptionsMenu(); + }); + + if (savedInstanceState == null) { + //noinspection ConstantConditions + updatePublicationValues(meshModel); + } + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + if (mIsConnected) { + getMenuInflater().inflate(R.menu.disconnect, menu); + } else { + getMenuInflater().inflate(R.menu.connect, menu); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + case R.id.action_connect: + mViewModel.navigateToScannerActivity(this, false, Utils.CONNECT_TO_NETWORK, false); + return true; + case R.id.action_disconnect: + mViewModel.disconnect(); + return true; + default: + return false; + } + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + } + + @Override + protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == Utils.SELECT_KEY) { + if (resultCode == RESULT_OK) { + final ApplicationKey appKey = data.getParcelableExtra(AppKeysActivity.RESULT_APP_KEY); + if (appKey != null) { + mAppKeyIndex = appKey.getKeyIndex(); + mAppKeyView.setText(getString(R.string.app_key_index, appKey.getKeyIndex())); + } + } + } + } + + @Override + protected void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putSerializable(RESULT_LABEL_UUID, mLabelUUID); + outState.putInt(RESULT_PUBLISH_ADDRESS, mPublishAddress); + outState.putInt(RESULT_APP_KEY_INDEX, mAppKeyIndex); + outState.putBoolean(RESULT_CREDENTIAL_FLAG, mActionFriendshipCredentialSwitch.isChecked()); + outState.putInt(RESULT_PUBLISH_TTL, mPublishTtl); + outState.putInt(RESULT_PUBLICATION_STEPS, mPublicationSteps); + outState.putInt(RESULT_PUBLICATION_RESOLUTION, mPublicationResolution); + outState.putInt(RESULT_PUBLISH_RETRANSMIT_COUNT, mRetransmitCount); + outState.putInt(RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS, mRetransmitIntervalSteps); + } + + @Override + protected void onRestoreInstanceState(final Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mLabelUUID = (UUID) savedInstanceState.getSerializable(RESULT_LABEL_UUID); + mPublishAddress = savedInstanceState.getInt(RESULT_PUBLISH_ADDRESS); + mAppKeyIndex = savedInstanceState.getInt(RESULT_APP_KEY_INDEX); + mPublishTtl = savedInstanceState.getInt(RESULT_PUBLISH_TTL); + mPublicationSteps = savedInstanceState.getInt(RESULT_PUBLICATION_STEPS); + mPublicationResolution = savedInstanceState.getInt(RESULT_PUBLICATION_RESOLUTION); + mRetransmitCount = savedInstanceState.getInt(RESULT_PUBLISH_RETRANSMIT_COUNT); + mRetransmitIntervalSteps = savedInstanceState.getInt(RESULT_PUBLISH_RETRANSMIT_INTERVAL_STEPS); + updateUi(savedInstanceState.getBoolean(RESULT_CREDENTIAL_FLAG)); + } + + @Override + public Group createGroup(@NonNull final String name) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + return network.createGroup(network.getSelectedProvisioner(), name); + } + return null; + } + + @Override + public Group createGroup(@NonNull final UUID uuid, final String name) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + return network.createGroup(uuid, null, name); + } + return null; + } + + @Override + public boolean onGroupAdded(@NonNull final Group group) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + if (network.addGroup(group)) { + onPublishAddressSet(group); + return true; + } + } + return false; + } + + @Override + public boolean onGroupAdded(@NonNull final String name, final int address) { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + final Group group = network.createGroup(network.getSelectedProvisioner(), address, name); + if (group != null) { + if (network.addGroup(group)) { + onPublishAddressSet(group); + return true; + } + } + } + return false; + } + + @Override + public void onPublishAddressSet(final int address) { + mLabelUUID = null; + mPublishAddress = address; + mPublishAddressView.setText(MeshAddress.formatAddress(address, true)); + } + + @Override + public void onPublishAddressSet(@NonNull final Group group) { + mLabelUUID = group.getAddressLabel(); + mPublishAddress = group.getAddress(); + mPublishAddressView.setText(MeshAddress.formatAddress(group.getAddress(), true)); + } + + @Override + public void setPublishTtl(final int ttl) { + mPublishTtl = ttl; + updateTtlUi(ttl); + } + + private void updatePublicationValues(@NonNull final MeshModel model) { + final PublicationSettings publicationSettings = model.getPublicationSettings(); + + //Default app key index to the 0th key in the list of bound app keys + if (!model.getBoundAppKeyIndexes().isEmpty()) { + mAppKeyIndex = mMeshModel.getBoundAppKeyIndexes().get(0); + } + + if (publicationSettings != null) { + mPublishAddress = publicationSettings.getPublishAddress(); + mLabelUUID = publicationSettings.getLabelUUID(); + + mActionFriendshipCredentialSwitch.setChecked(publicationSettings.getCredentialFlag()); + mPublishTtl = publicationSettings.getPublishTtl(); + + mPublicationSteps = publicationSettings.getPublicationSteps(); + mPublicationResolution = publicationSettings.getPublicationResolution(); + + mRetransmitCount = publicationSettings.getPublishRetransmitCount(); + mRetransmitIntervalSteps = publicationSettings.getPublishRetransmitIntervalSteps(); + + if (!model.getBoundAppKeyIndexes().isEmpty()) { + mAppKeyIndex = publicationSettings.getAppKeyIndex(); + } + updateUi(publicationSettings.getCredentialFlag()); + } + updateTtlUi(mPublishTtl); + } + + private void updateUi(final boolean credentialFlag) { + if (mLabelUUID == null) { + mPublishAddressView.setText(MeshAddress.formatAddress(mPublishAddress, true)); + } else { + mPublishAddressView.setText(mLabelUUID.toString().toUpperCase(Locale.US)); + } + + if (MeshAddress.isValidUnassignedAddress(mPublishAddress)) { + mPublishAddressView.setText(R.string.not_assigned); + } else { + mPublishAddressView.setText(MeshAddress.formatAddress(mPublishAddress, true)); + } + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + mAppKeyView.setText(getString(R.string.app_key_index, mAppKeyIndex)); + if (network != null) { + final ApplicationKey key = network.getAppKey(mAppKeyIndex); + if (key != null) { + mAppKeyView.setText(getString(R.string.app_key_name_and_index, key.getName(), mAppKeyIndex)); + } else { + mAppKeyView.setText(getString(R.string.unavailable)); + } + } else { + mAppKeyView.setText(getString(R.string.unavailable)); + } + + mActionFriendshipCredentialSwitch.setChecked(credentialFlag); + + final int period = PublicationSettings.getPublishPeriod(mPublicationResolution, mPublicationSteps); + mPublicationIntervalSeekBar.setProgress(mPublicationSteps); + mPublicationInterval.setText(getString(R.string.time_ms, period)); + + mRetransmissionCountSeekBar.setProgress(mRetransmitCount); + final int retransmissionInterval = PublicationSettings.getRetransmissionInterval(mRetransmitIntervalSteps); + mRetransmitIntervalSeekBar.setProgress(retransmissionInterval); + + } + + private void updateTtlUi(final int ttl) { + if (MeshParserUtils.isDefaultPublishTtl(ttl)) { + mPublishTtlView.setText(getString(R.string.uses_default_ttl)); + } else { + mPublishTtlView.setText(String.valueOf(ttl)); + } + } + + private void setPublication() { + final ProvisionedMeshNode node = mViewModel.getSelectedMeshNode().getValue(); + final Element element = mViewModel.getSelectedElement().getValue(); + final MeshModel model = mViewModel.getSelectedModel().getValue(); + final MeshMessage configModelPublicationSet; + if (node != null && element != null && model != null) { + final AddressType type = MeshAddress.getAddressType(mPublishAddress); + if (type != null && type != AddressType.VIRTUAL_ADDRESS) { + configModelPublicationSet = new ConfigModelPublicationSet(element.getElementAddress(), + mPublishAddress, mAppKeyIndex, mActionFriendshipCredentialSwitch.isChecked(), mPublishTtl, + mPublicationSteps, mPublicationResolution, mRetransmitCount, mRetransmitIntervalSteps, model.getModelId()); + } else { + configModelPublicationSet = new ConfigModelPublicationVirtualAddressSet(element.getElementAddress(), + mLabelUUID, mAppKeyIndex, mActionFriendshipCredentialSwitch.isChecked(), mPublishTtl, + mPublicationSteps, mPublicationResolution, mRetransmitCount, mRetransmitIntervalSteps, model.getModelId()); + } + try { + mViewModel.getMeshManagerApi().createMeshPdu(node.getUnicastAddress(), configModelPublicationSet); + } catch (IllegalArgumentException ex) { + final DialogFragmentError message = DialogFragmentError. + newInstance(getString(R.string.title_error), ex.getMessage()); + message.show(getSupportFragmentManager(), null); + return; + } + } + final Intent returnIntent = new Intent(); + setResult(Activity.RESULT_OK, returnIntent); + finish(); + } + + protected final boolean checkConnectivity() { + if (!mIsConnected) { + mViewModel.displayDisconnectedSnackBar(this, mContainer); + return false; + } + return true; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/VendorModelActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/VendorModelActivity.java similarity index 92% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/VendorModelActivity.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/VendorModelActivity.java index a4bfd120d..d1a489cbf 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/VendorModelActivity.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/VendorModelActivity.java @@ -1,10 +1,10 @@ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node; -import android.arch.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProvider; import android.os.Bundle; -import android.support.constraint.ConstraintLayout; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; +import androidx.constraintlayout.widget.ConstraintLayout; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -19,6 +19,7 @@ import javax.inject.Inject; import no.nordicsemi.android.meshprovisioner.models.VendorModel; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; import no.nordicsemi.android.meshprovisioner.transport.Element; import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; import no.nordicsemi.android.meshprovisioner.transport.MeshModel; @@ -26,6 +27,7 @@ import no.nordicsemi.android.meshprovisioner.transport.VendorModelMessageStatus; import no.nordicsemi.android.meshprovisioner.transport.VendorModelMessageUnacked; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; @@ -96,8 +98,8 @@ public void afterTextChanged(final Editable s) { actionSend.setOnClickListener(v -> { messageContainer.setVisibility(View.GONE); receivedMessage.setText(""); - final String opCode = opCodeEditText.getText().toString().trim(); - final String parameters = parametersEditText.getText().toString().trim(); + final String opCode = opCodeEditText.getEditableText().toString().trim(); + final String parameters = parametersEditText.getEditableText().toString().trim(); if (!validateOpcode(opCode, opCodeLayout)) return; @@ -212,14 +214,14 @@ public void sendVendorModelMessage(final int opcode, final byte[] parameters, fi final VendorModel model = (VendorModel) mViewModel.getSelectedModel().getValue(); if (model != null) { final int appKeyIndex = model.getBoundAppKeyIndexes().get(0); - final byte[] appKey = model.getBoundAppKey(appKeyIndex).getKey(); + final ApplicationKey appKey = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(appKeyIndex); final MeshMessage message; if (acknowledged) { message = new VendorModelMessageAcked(appKey, model.getModelId(), model.getCompanyIdentifier(), opcode, parameters); } else { message = new VendorModelMessageUnacked(appKey, model.getModelId(), model.getCompanyIdentifier(), opcode, parameters); } - mViewModel.getMeshManagerApi().sendMeshMessage(element.getElementAddress(), message); + sendMessage(element.getElementAddress(), message); } } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ElementAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/adapter/ElementAdapter.java similarity index 73% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ElementAdapter.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/adapter/ElementAdapter.java index 9f27ff769..77eb2c35f 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/ElementAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/adapter/ElementAdapter.java @@ -20,16 +20,13 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.adapter; +package no.nordicsemi.android.nrfmeshprovisioner.node.adapter; -import android.arch.lifecycle.LiveData; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.constraint.ConstraintLayout; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -37,6 +34,11 @@ import java.util.ArrayList; import java.util.List; +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.models.VendorModel; @@ -44,21 +46,18 @@ import no.nordicsemi.android.meshprovisioner.transport.MeshModel; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; import no.nordicsemi.android.meshprovisioner.utils.CompositionDataParser; -import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; -import no.nordicsemi.android.nrfmeshprovisioner.NodeConfigurationActivity; import no.nordicsemi.android.nrfmeshprovisioner.R; public class ElementAdapter extends RecyclerView.Adapter { private final Context mContext; private final List mElements = new ArrayList<>(); - private final String TAG = ElementAdapter.class.getSimpleName(); private OnItemClickListener mOnItemClickListener; private ProvisionedMeshNode mProvisionedMeshNode; - public ElementAdapter(final NodeConfigurationActivity nodeConfigurationActivity, final LiveData meshNodeLiveData) { - this.mContext = nodeConfigurationActivity.getApplicationContext(); - meshNodeLiveData.observe(nodeConfigurationActivity, meshNode -> { + public ElementAdapter(@NonNull final Context context, @NonNull final LiveData meshNodeLiveData) { + this.mContext = context.getApplicationContext(); + meshNodeLiveData.observe((LifecycleOwner) context, meshNode -> { if (meshNode != null) { mProvisionedMeshNode = meshNode; mElements.clear(); @@ -69,7 +68,7 @@ public ElementAdapter(final NodeConfigurationActivity nodeConfigurationActivity, } - public void setOnItemClickListener(final ElementAdapter.OnItemClickListener listener) { + public void setOnItemClickListener(@NonNull final ElementAdapter.OnItemClickListener listener) { mOnItemClickListener = listener; } @@ -83,24 +82,23 @@ public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int @Override public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) { final Element element = mElements.get(position); - holder.mElementContainer.setTag(element.getElementAddress()); final int modelCount = element.getMeshModels().size(); - holder.mElementTitle.setText(mContext.getString(R.string.element_address, MeshAddress.formatAddress(element.getElementAddress(), true))); + holder.mElementTitle.setText(element.getName()); holder.mElementSubtitle.setText(mContext.getString(R.string.model_count, modelCount)); final List models = new ArrayList<>(element.getMeshModels().values()); inflateModelViews(holder, models); } - private void inflateModelViews(final ViewHolder holder, final List models) { //Remove all child views to avoid duplicating holder.mModelContainer.removeAllViews(); - for (MeshModel model : models) { + for (int i = 0; i < models.size(); i++) { + final MeshModel model = models.get(i); final View modelView = LayoutInflater.from(mContext).inflate(R.layout.model_item, holder.mElementContainer, false); modelView.setTag(model.getModelId()); - final TextView modelNameView = modelView.findViewById(R.id.address); - final TextView modelIdView = modelView.findViewById(R.id.model_id); + final TextView modelNameView = modelView.findViewById(R.id.title); + final TextView modelIdView = modelView.findViewById(R.id.subtitle); modelNameView.setText(model.getModelName()); if (model instanceof VendorModel) { modelIdView.setText(mContext.getString(R.string.format_vendor_model_id, CompositionDataParser.formatModelIdentifier(model.getModelId(), true))); @@ -111,8 +109,7 @@ private void inflateModelViews(final ViewHolder holder, final List mo modelView.setOnClickListener(v -> { final int position = holder.getAdapterPosition(); final Element element = mElements.get(position); - final MeshModel model1 = element.getMeshModels().get(v.getTag()); - mOnItemClickListener.onElementItemClick(mProvisionedMeshNode, element, model1); + mOnItemClickListener.onModelClicked(mProvisionedMeshNode, element, model); }); holder.mModelContainer.addView(modelView); } @@ -132,9 +129,10 @@ public boolean isEmpty() { return getItemCount() == 0; } - @FunctionalInterface public interface OnItemClickListener { - void onElementItemClick(final ProvisionedMeshNode meshNode, final Element element, final MeshModel model); + void onElementClicked(@NonNull final Element element); + + void onModelClicked(@NonNull final ProvisionedMeshNode meshNode, @NonNull final Element element, @NonNull final MeshModel model); } final class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { @@ -147,31 +145,31 @@ final class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickLi @BindView(R.id.element_subtitle) TextView mElementSubtitle; @BindView(R.id.element_expand) - ImageView mElementExpand; + ImageButton mElementExpand; + @BindView(R.id.edit) + ImageButton mEdit; @BindView(R.id.model_container) LinearLayout mModelContainer; private ViewHolder(final View view) { super(view); ButterKnife.bind(this, view); - mElementContainer.setOnClickListener(this); - + mElementExpand.setOnClickListener(this); + mEdit.setOnClickListener(this); } @Override public void onClick(final View v) { - switch (v.getId()) { - case R.id.element_item_container: - if (mModelContainer.getVisibility() == View.VISIBLE) { - mElementExpand.setImageResource(R.drawable.ic_round_expand_more_black_alpha_24dp); - mModelContainer.setVisibility(View.GONE); - } else { - mElementExpand.setImageResource(R.drawable.ic_round_expand_less_black_alpha_24dp); - mModelContainer.setVisibility(View.VISIBLE); - } - break; - default: - break; + if (v.getId() == R.id.element_expand) { + if (mModelContainer.getVisibility() == View.VISIBLE) { + mElementExpand.setImageResource(R.drawable.ic_round_expand_more_black_alpha_24dp); + mModelContainer.setVisibility(View.GONE); + } else { + mElementExpand.setImageResource(R.drawable.ic_round_expand_less_black_alpha_24dp); + mModelContainer.setVisibility(View.VISIBLE); + } + } else if (v.getId() == R.id.edit) { + mOnItemClickListener.onElementClicked(mElements.get(getAdapterPosition())); } } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/NodeAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/adapter/NodeAdapter.java similarity index 61% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/NodeAdapter.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/adapter/NodeAdapter.java index 90808fb39..624eff14a 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/adapter/NodeAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/adapter/NodeAdapter.java @@ -20,13 +20,9 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.adapter; +package no.nordicsemi.android.nrfmeshprovisioner.node.adapter; -import android.arch.lifecycle.LiveData; -import android.support.annotation.NonNull; -import android.support.v4.app.FragmentActivity; -import android.support.v7.widget.AppCompatImageButton; -import android.support.v7.widget.RecyclerView; +import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -37,33 +33,38 @@ import java.util.List; import java.util.Map; +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.transport.Element; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.meshprovisioner.utils.AddressUtils; import no.nordicsemi.android.meshprovisioner.utils.CompanyIdentifiers; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; public class NodeAdapter extends RecyclerView.Adapter { - private Integer mUnicastAddress; - private int mNodeIndex = -1; - private final FragmentActivity mContext; + private final Context mContext; private final List mNodes = new ArrayList<>(); private OnItemClickListener mOnItemClickListener; - public NodeAdapter(final FragmentActivity fragmentActivity, LiveData> provisionedNodesLiveData) { - this.mContext = fragmentActivity; - provisionedNodesLiveData.observe(fragmentActivity, provisionedNodes -> { - if (provisionedNodes != null) { + public NodeAdapter(@NonNull final Context context, + @NonNull final LiveData> provisionedNodesLiveData) { + this.mContext = context; + provisionedNodesLiveData.observe((LifecycleOwner) context, nodes -> { + if (nodes != null) { mNodes.clear(); - mNodes.addAll(provisionedNodes); + mNodes.addAll(nodes); + notifyDataSetChanged(); } }); } - public void setOnItemClickListener(final OnItemClickListener listener) { + public void setOnItemClickListener(@NonNull final OnItemClickListener listener) { mOnItemClickListener = listener; } @@ -77,18 +78,25 @@ public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int @Override public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) { final ProvisionedMeshNode node = mNodes.get(position); - holder.name.setText(node.getNodeName()); - holder.unicastAddress.setText(MeshParserUtils.bytesToHex(AddressUtils.getUnicastAddressBytes(node.getUnicastAddress()), false)); - final Map elements = node.getElements(); - if (!elements.isEmpty()) { - holder.nodeInfoContainer.setVisibility(View.VISIBLE); - holder.companyIdentifier.setText(CompanyIdentifiers.getCompanyName(node.getCompanyIdentifier().shortValue())); - holder.elements.setText(String.valueOf(elements.size())); - holder.models.setText(String.valueOf(getModels(elements))); - } else { - holder.companyIdentifier.setText(R.string.unknown); - holder.elements.setText(String.valueOf(node.getNumberOfElements())); - holder.models.setText(R.string.unknown); + if (node != null) { + holder.name.setText(node.getNodeName()); + holder.unicastAddress.setText(MeshParserUtils.bytesToHex(MeshAddress.addressIntToBytes(node.getUnicastAddress()), false)); + final Map elements = node.getElements(); + if (!elements.isEmpty()) { + holder.nodeInfoContainer.setVisibility(View.VISIBLE); + if (node.getCompanyIdentifier() != null) { + holder.companyIdentifier.setText(CompanyIdentifiers.getCompanyName(node.getCompanyIdentifier().shortValue())); + } else { + holder.companyIdentifier.setText(R.string.unknown); + } + holder.elements.setText(String.valueOf(elements.size())); + holder.models.setText(String.valueOf(getModels(elements))); + } else { + holder.companyIdentifier.setText(R.string.unknown); + holder.elements.setText(String.valueOf(node.getNumberOfElements())); + holder.models.setText(R.string.unknown); + } + //holder.getSwipeableView().setTag(node); } } @@ -97,6 +105,13 @@ public int getItemCount() { return mNodes.size(); } + public ProvisionedMeshNode getItem(final int position) { + if (mNodes.size() > 0 && position > -1) { + return mNodes.get(position); + } + return null; + } + public boolean isEmpty() { return getItemCount() == 0; } @@ -109,34 +124,12 @@ private int getModels(final Map elements) { return models; } - public void selectConnectedMeshNode(final Integer unicastAddress) { - if (unicastAddress != null) { - final int index = mNodeIndex = getMeshNodeIndex(unicastAddress); - if (index > -1) { - notifyItemChanged(mNodeIndex); - } - } else { - notifyItemChanged(mNodeIndex); - } - mUnicastAddress = unicastAddress; - } - - private int getMeshNodeIndex(final int unicastAddress) { - for (int i = 0; i < mNodes.size(); i++) { - if (unicastAddress == mNodes.get(i).getUnicastAddress()) { - return i; - } - } - return -1; - } - + @FunctionalInterface public interface OnItemClickListener { void onConfigureClicked(final ProvisionedMeshNode node); - - void onDetailsClicked(final ProvisionedMeshNode node); } - final class ViewHolder extends RecyclerView.ViewHolder { + final class ViewHolder extends RemovableViewHolder { @BindView(R.id.container) FrameLayout container; @@ -152,25 +145,13 @@ final class ViewHolder extends RecyclerView.ViewHolder { TextView elements; @BindView(R.id.models) TextView models; - @BindView(R.id.action_configure) - AppCompatImageButton configure; private ViewHolder(final View provisionedView) { super(provisionedView); ButterKnife.bind(this, provisionedView); - - configure.setOnClickListener(v -> { - - if (mOnItemClickListener != null) { - mOnItemClickListener.onConfigureClicked(mNodes.get(getAdapterPosition())); - } - - }); - container.setOnClickListener(v -> { - if (mOnItemClickListener != null) { - mOnItemClickListener.onDetailsClicked(mNodes.get(getAdapterPosition())); + mOnItemClickListener.onConfigureClicked(mNodes.get(getAdapterPosition())); } }); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetDetailsDialogFragment.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetDetailsDialogFragment.java similarity index 88% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetDetailsDialogFragment.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetDetailsDialogFragment.java index 510ba703f..109aba4d4 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetDetailsDialogFragment.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetDetailsDialogFragment.java @@ -1,15 +1,15 @@ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; import android.app.Activity; -import android.arch.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProvider; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.BottomSheetDialogFragment; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -24,6 +24,7 @@ import no.nordicsemi.android.meshprovisioner.Group; import no.nordicsemi.android.meshprovisioner.transport.Element; import no.nordicsemi.android.meshprovisioner.transport.MeshModel; +import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.adapter.GroupModelAdapter; public class BottomSheetDetailsDialogFragment extends BottomSheetDialogFragment implements GroupModelAdapter.OnItemClickListener { @@ -84,7 +85,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final final Button actionApply = rootView.findViewById(R.id.action_apply); actionApply.setOnClickListener(v -> { - final String groupName = mGroupNameTextInput.getEditableText().toString(); + final String groupName = mGroupNameTextInput.getEditableText().toString().trim(); if (validateInput(groupName)) { hideKeyboard(); mGroupNameTextInput.clearFocus(); @@ -114,7 +115,7 @@ private boolean validateInput(final String groupName) { return !TextUtils.isEmpty(groupName); } - public void hideKeyboard() { + private void hideKeyboard() { final InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(mGroupNameTextInput.getWindowToken(), 0); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetLevelDialogFragment.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetLevelDialogFragment.java similarity index 94% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetLevelDialogFragment.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetLevelDialogFragment.java index 504801cc6..cc8a6d681 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetLevelDialogFragment.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetLevelDialogFragment.java @@ -1,9 +1,9 @@ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.BottomSheetDialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,6 +11,7 @@ import android.widget.TextView; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; public class BottomSheetLevelDialogFragment extends BottomSheetDialogFragment { private static final String KEY_INDEX = "KEY_INDEX"; @@ -20,7 +21,7 @@ public class BottomSheetLevelDialogFragment extends BottomSheetDialogFragment { private int mTransitionStepResolution; private int mTransitionSteps; - interface BottomSheetLevelListener { + public interface BottomSheetLevelListener { void toggleLevel(final int keyIndex, final int level, final int mTransitionSteps, final int mTransitionStepResolution, final int progress); } @@ -57,7 +58,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final final TextView delayTime = nodeControlsContainer.findViewById(R.id.delay_time); final TextView level = nodeControlsContainer.findViewById(R.id.level); - final SeekBar levelSeekBar = nodeControlsContainer.findViewById(R.id.level_seekbar); + final SeekBar levelSeekBar = nodeControlsContainer.findViewById(R.id.level_seek_bar); levelSeekBar.setProgress(0); levelSeekBar.setMax(100); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetOnOffDialogFragment.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetOnOffDialogFragment.java similarity index 94% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetOnOffDialogFragment.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetOnOffDialogFragment.java index 5528bea51..0d244417b 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetOnOffDialogFragment.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetOnOffDialogFragment.java @@ -1,9 +1,9 @@ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.BottomSheetDialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -13,14 +13,15 @@ import android.widget.Toast; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; public class BottomSheetOnOffDialogFragment extends BottomSheetDialogFragment { private static final String KEY_INDEX = "KEY_INDEX"; private int mKeyIndex; - protected int mTransitionStepResolution; - protected int mTransitionSteps; + private int mTransitionStepResolution; + private int mTransitionSteps; - interface BottomSheetOnOffListener { + public interface BottomSheetOnOffListener { void toggle(final int mKeyIndex, final boolean state, final int mTransitionSteps, final int mTransitionStepResolution, final int progress); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetVendorDialogFragment.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetVendorDialogFragment.java similarity index 92% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetVendorDialogFragment.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetVendorDialogFragment.java index bd2312130..6d8757767 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/BottomSheetVendorDialogFragment.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/BottomSheetVendorDialogFragment.java @@ -1,11 +1,6 @@ -package no.nordicsemi.android.nrfmeshprovisioner; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.BottomSheetDialogFragment; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -17,7 +12,14 @@ import android.widget.CheckBox; import android.widget.TextView; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; @@ -31,7 +33,7 @@ public class BottomSheetVendorDialogFragment extends BottomSheetDialogFragment { private View messageContainer; private TextView receivedMessage; - interface BottomSheetVendorModelControlsListener { + public interface BottomSheetVendorModelControlsListener { void sendVendorModelMessage(final int modelId, final int keyIndex, final int opCode, final byte[] parameters, final boolean acknowledged); } @@ -109,8 +111,8 @@ public void afterTextChanged(final Editable s) { actionSend.setOnClickListener(v -> { messageContainer.setVisibility(View.GONE); receivedMessage.setText(""); - final String opCode = opCodeEditText.getText().toString().trim(); - final String parameters = parametersEditText.getText().toString().trim(); + final String opCode = opCodeEditText.getEditableText().toString().trim(); + final String parameters = parametersEditText.getEditableText().toString().trim(); if (!validateOpcode(opCode, opCodeLayout)) return; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentElementName.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentElementName.java new file mode 100644 index 000000000..e6753ddc8 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentElementName.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.transport.Element; +import no.nordicsemi.android.nrfmeshprovisioner.R; + +public class DialogFragmentElementName extends DialogFragment { + + private static final String ELEMENT = "ELEMENT"; + + //UI Bindings + @BindView(R.id.text_input_layout) + TextInputLayout elementNameInputLayout; + @BindView(R.id.text_input) + TextInputEditText elementNameInput; + + private Element mElement; + + public static DialogFragmentElementName newInstance(@NonNull final Element element) { + DialogFragmentElementName fragmentNetworkKey = new DialogFragmentElementName(); + final Bundle args = new Bundle(); + args.putParcelable(ELEMENT, element); + fragmentNetworkKey.setArguments(args); + return fragmentNetworkKey; + } + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mElement = getArguments().getParcelable(ELEMENT); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_name, null); + + //Bind ui + ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); + elementNameInputLayout.setHint(getString(R.string.hint_friendly_name)); + elementNameInput.setText(mElement.getName()); + elementNameInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + if (TextUtils.isEmpty(s.toString().trim())) { + elementNameInputLayout.setError(getString(R.string.error_empty_name)); + } else { + elementNameInputLayout.setError(null); + } + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }); + + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) + .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); + + alertDialogBuilder.setIcon(R.drawable.ic_label_black_alpha_24dp); + alertDialogBuilder.setTitle(R.string.title_element_name); + summary.setText(R.string.element_name_rationale); + + final AlertDialog alertDialog = alertDialogBuilder.show(); + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { + final String elementName = elementNameInput.getEditableText().toString().trim(); + if (!TextUtils.isEmpty(elementName)) { + if (((DialogFragmentElementNameListener) requireContext()).onElementNameUpdated(mElement, elementName)) { + dismiss(); + } + } + }); + + return alertDialog; + } + + public interface DialogFragmentElementNameListener { + + boolean onElementNameUpdated(@NonNull final Element element, @NonNull final String name); + + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNetworkTransmitSettings.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentNetworkTransmitSettings.java similarity index 80% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNetworkTransmitSettings.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentNetworkTransmitSettings.java index ca22a4f31..64cd16436 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNetworkTransmitSettings.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentNetworkTransmitSettings.java @@ -1,12 +1,13 @@ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; -import android.app.AlertDialog; +import android.annotation.SuppressLint; +import androidx.appcompat.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.widget.SeekBar; @@ -21,8 +22,8 @@ public class DialogFragmentNetworkTransmitSettings extends DialogFragment { private static final String TAG = DialogFragmentNetworkTransmitSettings.class.getSimpleName(); - public static final String TRANSMIT_COUNT = "TRANSMIT_COUNT"; - public static final String TRANSMIT_INTERVAL_STEPS = "TRANSMIT_INTERVAL_STEPS"; + private static final String TRANSMIT_COUNT = "TRANSMIT_COUNT"; + private static final String TRANSMIT_INTERVAL_STEPS = "TRANSMIT_INTERVAL_STEPS"; private static final int MIN_TRANSMIT_COUNT = 0; private static final int MAX_TRANSMIT_COUNT = 0b111; @@ -64,6 +65,7 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_network_transmit_settings, null); ButterKnife.bind(this, rootView); @@ -108,7 +110,7 @@ public void onStopTrackingTouch(SeekBar seekBar) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); alertDialogBuilder.setIcon(R.drawable.ic_repeat_black_24dp); @@ -116,13 +118,8 @@ public void onStopTrackingTouch(SeekBar seekBar) { final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - if (getParentFragment() == null) { - ((DialogFragmentNetworkTransmitSettings.DialogFragmentNetworkTransmitSettingsListener) getActivity()) - .onNetworkTransmitSettingsEntered(mTransmitCount, mTransmitIntervalSteps); - } else { - ((DialogFragmentNetworkTransmitSettings.DialogFragmentNetworkTransmitSettingsListener) getParentFragment()) - .onNetworkTransmitSettingsEntered(mTransmitCount, mTransmitIntervalSteps); - } + ((DialogFragmentNetworkTransmitSettings.DialogFragmentNetworkTransmitSettingsListener) requireActivity()) + .onNetworkTransmitSettingsEntered(mTransmitCount, mTransmitIntervalSteps); dismiss(); }); @@ -132,15 +129,15 @@ public void onStopTrackingTouch(SeekBar seekBar) { private void setTransmitCount(final int transmitCount) { mTransmitCount = transmitCount; final int transmitCountActual = mTransmitCount + 1; - networkTransmitCountText.setText(getResources().getString( - R.string.text_network_transmit_count, transmitCountActual)); + networkTransmitCountText.setText(getResources().getQuantityString( + R.plurals.transmit_count, transmitCountActual, transmitCountActual)); } private void setTransmitIntervalSteps(final int transmitIntervalSteps) { mTransmitIntervalSteps = transmitIntervalSteps; final int transmitIntervalMilliseconds = (mTransmitIntervalSteps + 1) * 10; networkTransmitIntervalStepsText.setText(getResources().getString( - R.string.text_network_transmit_interval_steps, transmitIntervalMilliseconds)); + R.string.time_ms, transmitIntervalMilliseconds)); } public interface DialogFragmentNetworkTransmitSettingsListener { diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNodeName.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentNodeName.java similarity index 76% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNodeName.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentNodeName.java index 28b9b40cd..a6c5db201 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentNodeName.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentNodeName.java @@ -20,23 +20,26 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; +import android.widget.TextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.nrfmeshprovisioner.R; @@ -72,11 +75,12 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_name, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_name, null); //Bind ui ButterKnife.bind(this, rootView); - nodeNameInputLayout.setHint(getString(R.string.hint_node_name)); + final TextView summary = rootView.findViewById(R.id.summary); + nodeNameInputLayout.setHint(getString(R.string.hint_friendly_name)); nodeNameInput.setText(mNodeName); nodeNameInput.addTextChangedListener(new TextWatcher() { @Override @@ -86,8 +90,8 @@ public void beforeTextChanged(final CharSequence s, final int start, final int c @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { - if (TextUtils.isEmpty(s.toString())) { - nodeNameInputLayout.setError(getString(R.string.error_node_name)); + if (TextUtils.isEmpty(s.toString().trim())) { + nodeNameInputLayout.setError(getString(R.string.error_empty_name)); } else { nodeNameInputLayout.setError(null); } @@ -99,19 +103,20 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); - alertDialogBuilder.setIcon(R.drawable.ic_vpn_key_black_alpha_24dp); + alertDialogBuilder.setIcon(R.drawable.ic_label_black_alpha_24dp); alertDialogBuilder.setTitle(R.string.title_node_name); - alertDialogBuilder.setMessage(R.string.name_rationale); + summary.setText(R.string.node_name_rationale); final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String nodeName = nodeNameInput.getText().toString(); + final String nodeName = nodeNameInput.getEditableText().toString().trim(); if (!TextUtils.isEmpty(nodeName)) { - ((DialogFragmentNodeNameListener) getContext()).onNodeNameUpdated(nodeName); - dismiss(); + if (((DialogFragmentNodeNameListener) requireContext()).onNodeNameUpdated(nodeName)) { + dismiss(); + } } }); @@ -120,7 +125,7 @@ public void afterTextChanged(final Editable s) { public interface DialogFragmentNodeNameListener { - void onNodeNameUpdated(final String nodeName); + boolean onNodeNameUpdated(@NonNull final String nodeName); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPubRetransmitIntervalSteps.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPubRetransmitIntervalSteps.java similarity index 83% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPubRetransmitIntervalSteps.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPubRetransmitIntervalSteps.java index 6674ecaf0..e874ef9d5 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPubRetransmitIntervalSteps.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPubRetransmitIntervalSteps.java @@ -20,17 +20,18 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; -import android.app.AlertDialog; +import android.annotation.SuppressLint; +import androidx.appcompat.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import androidx.fragment.app.DialogFragment; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; @@ -52,7 +53,7 @@ public class DialogFragmentPubRetransmitIntervalSteps extends DialogFragment { @BindView(R.id.text_input_layout) TextInputLayout intervalStepsInputLayout; @BindView(R.id.text_input) - TextInputEditText intevalStepsInput; + TextInputEditText intervalStepsInput; private int mRetransmitCount; @@ -75,6 +76,7 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_publication_parameters, null); //Bind ui @@ -82,11 +84,11 @@ public Dialog onCreateDialog(final Bundle savedInstanceState) { ((TextView)rootView.findViewById(R.id.summary)).setText(R.string.dialog_summary_interval_steps); final String retransmitCount = String.valueOf(mRetransmitCount); - intevalStepsInput.setInputType(InputType.TYPE_CLASS_NUMBER); + intervalStepsInput.setInputType(InputType.TYPE_CLASS_NUMBER); intervalStepsInputLayout.setHint(getString(R.string.hint_publication_interval_steps)); - intevalStepsInput.setText(retransmitCount); - intevalStepsInput.setSelection(retransmitCount.length()); - intevalStepsInput.addTextChangedListener(new TextWatcher() { + intervalStepsInput.setText(retransmitCount); + intervalStepsInput.setSelection(retransmitCount.length()); + intervalStepsInput.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { @@ -107,7 +109,7 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); alertDialogBuilder.setIcon(R.drawable.ic_index); @@ -115,13 +117,10 @@ public void afterTextChanged(final Editable s) { final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String ivIndexInput = this.intevalStepsInput.getText().toString(); + final String ivIndexInput = this.intervalStepsInput.getEditableText().toString().trim(); if (validateInput(ivIndexInput)) { - if (getParentFragment() == null) { - ((DialogFragmentIntervalStepsListener) getActivity()).setRetransmitIntervalSteps(Integer.parseInt(ivIndexInput, 16)); - } else { - ((DialogFragmentIntervalStepsListener) getParentFragment()).setRetransmitIntervalSteps(Integer.parseInt(ivIndexInput, 16)); - } + ((DialogFragmentIntervalStepsListener) requireActivity()). + setRetransmitIntervalSteps(Integer.parseInt(ivIndexInput, 16)); dismiss(); } }); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublicationResolution.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublicationResolution.java similarity index 86% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublicationResolution.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublicationResolution.java index 3c1ad9c87..e6b437766 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublicationResolution.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublicationResolution.java @@ -1,12 +1,12 @@ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; -import android.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; @@ -36,16 +36,16 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()) .setIcon(R.drawable.ic_linear_scale_black_alpha_24dp) .setTitle(R.string.title_publication_resolution) .setSingleChoiceItems(R.array.arr_publication_resolution, mPublicationResolution, null) .setPositiveButton(R.string.ok, (dialog, which) -> { final int index = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); - ((DialogFragmentPublicationResolutionListener) getActivity()).setPublicationResolution(getResolution(index)); }) + ((DialogFragmentPublicationResolutionListener) requireActivity()).setPublicationResolution(getResolution(index));}) .setNegativeButton(R.string.cancel, (dialog, which) -> { final int index = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); - ((DialogFragmentPublicationResolutionListener) getActivity()).setPublicationResolution(getResolution(index)); + ((DialogFragmentPublicationResolutionListener) requireActivity()).setPublicationResolution(getResolution(index)); }); return alertDialogBuilder.create(); diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublicationSteps.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublicationSteps.java similarity index 86% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublicationSteps.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublicationSteps.java index 145c4437e..51caeea14 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublicationSteps.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublicationSteps.java @@ -20,17 +20,18 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; -import android.app.AlertDialog; +import android.annotation.SuppressLint; +import androidx.appcompat.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import androidx.fragment.app.DialogFragment; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; @@ -75,11 +76,13 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_publication_parameters, null); //Bind ui ButterKnife.bind(this, rootView); - ((TextView)rootView.findViewById(R.id.summary)).setText(R.string.dialog_summary_publication_steps); + ((TextView)rootView.findViewById(R.id.summary)). + setText(R.string.dialog_summary_publication_steps); final String publicationSteps = String.valueOf(mPublicationSteps); publicationStepsInput.setInputType(InputType.TYPE_CLASS_NUMBER); @@ -107,7 +110,7 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); alertDialogBuilder.setIcon(R.drawable.ic_index); @@ -115,13 +118,10 @@ public void afterTextChanged(final Editable s) { final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String publicationStepsInput = this.publicationStepsInput.getText().toString(); + final String publicationStepsInput = this.publicationStepsInput.getEditableText().toString().trim(); if (validateInput(publicationStepsInput)) { - if (getParentFragment() == null) { - ((DialogFragmentPublicationStepsListener) getActivity()).setPublicationSteps(Integer.parseInt(publicationStepsInput, 16)); - } else { - ((DialogFragmentPublicationStepsListener) getParentFragment()).setPublicationSteps(Integer.parseInt(publicationStepsInput, 16)); - } + ((DialogFragmentPublicationStepsListener) requireActivity()). + setPublicationSteps(Integer.valueOf(publicationStepsInput)); dismiss(); } }); @@ -129,7 +129,6 @@ public void afterTextChanged(final Editable s) { return alertDialog; } - private boolean validateInput(final String input) { try { diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublishAddress.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublishAddress.java new file mode 100644 index 000000000..980707350 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublishAddress.java @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.KeyListener; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.RadioButton; +import android.widget.Spinner; +import android.widget.TextView; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import java.util.ArrayList; +import java.util.Locale; +import java.util.UUID; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.Group; +import no.nordicsemi.android.meshprovisioner.transport.PublicationSettings; +import no.nordicsemi.android.meshprovisioner.utils.AddressType; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.GroupCallbacks; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.adapter.AddressTypeAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.adapter.GroupAdapterSpinner; +import no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes; +import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; + +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.ALL_FRIENDS; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.ALL_NODES; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.ALL_PROXIES; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.ALL_RELAYS; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.GROUP_ADDRESS; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.UNICAST_ADDRESS; +import static no.nordicsemi.android.nrfmeshprovisioner.utils.AddressTypes.VIRTUAL_ADDRESS; + +public class DialogFragmentPublishAddress extends DialogFragment { + + private static final String PUBLICATION_SETTINGS = "PUBLICATION_SETTINGS"; + private static final String GROUPS = "GROUPS"; + private static final String GROUP = "GROUP"; + private static final String UUID_KEY = "UUID"; + private ArrayList mGroups = new ArrayList<>(); + private PublicationSettings mPublicationSettings; + private static final AddressTypes[] addressTypes = {UNICAST_ADDRESS, GROUP_ADDRESS, ALL_PROXIES, ALL_FRIENDS, ALL_RELAYS, ALL_NODES, VIRTUAL_ADDRESS}; + + //UI Bindings + @BindView(R.id.summary) + TextView summary; + @BindView(R.id.address_types) + Spinner addressTypesSpinnerView; + @BindView(R.id.label_container) + View labelContainer; + @BindView(R.id.uuid_label) + TextView labelUuidView; + @BindView(R.id.group_container) + View groupContainer; + @BindView(R.id.radio_select_group) + RadioButton selectGroup; + @BindView(R.id.radio_create_group) + RadioButton createGroup; + @BindView(R.id.groups) + Spinner groups; + @BindView(R.id.group_name_layout) + TextInputLayout groupNameInputLayout; + @BindView(R.id.name_input) + TextInputEditText groupNameInput; + @BindView(R.id.group_address_layout) + TextInputLayout addressInputLayout; + @BindView(R.id.address_input) + TextInputEditText addressInput; + @BindView(R.id.no_groups_configured) + TextView noGroups; + private Button mGenerateLabelUUID; + + private AddressTypeAdapter mAdapterSpinner; + private Group mGroup; + + public interface DialogFragmentPublicationListener { + + void onPublishAddressSet(final int publishAddress); + + void onPublishAddressSet(@NonNull final Group group); + + } + + public static DialogFragmentPublishAddress newInstance(@NonNull final PublicationSettings publicationSettings, + @NonNull final ArrayList groups) { + DialogFragmentPublishAddress fragmentPublishAddress = new DialogFragmentPublishAddress(); + final Bundle args = new Bundle(); + args.putParcelable(PUBLICATION_SETTINGS, publicationSettings); + args.putParcelableArrayList(GROUPS, groups); + fragmentPublishAddress.setArguments(args); + return fragmentPublishAddress; + } + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mPublicationSettings = getArguments().getParcelable(PUBLICATION_SETTINGS); + mGroups = getArguments().getParcelableArrayList(GROUPS); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()). + inflate(R.layout.dialog_fragment_group_subscription, null); + + //Bind ui + ButterKnife.bind(this, rootView); + if (savedInstanceState != null) { + labelUuidView.setText(savedInstanceState.getString(UUID_KEY)); + mGroup = savedInstanceState.getParcelable(GROUP); + } else { + mGroup = ((GroupCallbacks) requireActivity()).createGroup(); + } + + setAddressType(); + summary.setText(R.string.publish_address_dialog_summary); + addressTypesSpinnerView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(final AdapterView parent, final View view, final int position, final long id) { + updateAddress(mAdapterSpinner.getItem(position)); + } + + @Override + public void onNothingSelected(final AdapterView parent) { + + } + }); + + selectGroup.setOnCheckedChangeListener((buttonView, isChecked) -> { + groupNameInputLayout.setEnabled(!isChecked); + groupNameInputLayout.setError(null); + addressInputLayout.setEnabled(!isChecked); + addressInputLayout.setError(null); + groups.setEnabled(isChecked); + createGroup.setChecked(!isChecked); + }); + + createGroup.setOnCheckedChangeListener((buttonView, isChecked) -> { + groupNameInputLayout.setEnabled(isChecked); + groupNameInputLayout.setError(null); + addressInputLayout.setEnabled(isChecked); + addressInputLayout.setError(null); + groups.setEnabled(!isChecked); + selectGroup.setChecked(!isChecked); + }); + + final GroupAdapterSpinner adapter = new GroupAdapterSpinner(requireContext(), mGroups); + groups.setAdapter(adapter); + + if (mGroups.isEmpty()) { + selectGroup.setEnabled(false); + groups.setEnabled(false); + createGroup.setChecked(true); + } else { + selectGroup.setChecked(true); + createGroup.setChecked(false); + } + + final KeyListener hexKeyListener = new HexKeyListener(); + addressInput.setKeyListener(hexKeyListener); + addressInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + mGroup = null; + if (TextUtils.isEmpty(s.toString())) { + addressInputLayout.setError(getString(R.string.error_empty_group_address)); + } else { + addressInputLayout.setError(null); + } + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }); + + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()). + setIcon(R.drawable.ic_lan_black_alpha_24dp). + setTitle(R.string.title_publish_address). + setView(rootView). + setPositiveButton(R.string.ok, null). + setNegativeButton(R.string.cancel, null). + setNeutralButton(R.string.generate_uuid, null); + + final AlertDialog alertDialog = alertDialogBuilder.show(); + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> setPublishAddress()); + + mGenerateLabelUUID = alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL); + mGenerateLabelUUID.setOnClickListener(v -> { + final UUID uuid = MeshAddress.generateRandomLabelUUID(); + labelUuidView.setText(uuid.toString().toUpperCase(Locale.US)); + generateVirtualAddress(uuid); + }); + + return alertDialog; + } + + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(UUID_KEY, labelUuidView.getText().toString()); + outState.putParcelable(GROUP, mGroup); + } + + private void setPublishAddress() { + final String input = addressInput.getEditableText().toString().trim(); + final int address; + final AddressTypes type = (AddressTypes) addressTypesSpinnerView.getSelectedItem(); + switch (type) { + case UNICAST_ADDRESS: + if (validateInput(input)) { + address = Integer.parseInt(input, 16); + ((DialogFragmentPublicationListener) requireActivity()) + .onPublishAddressSet(address); + dismiss(); + } + break; + case GROUP_ADDRESS: + try { + if (createGroup.isChecked()) { + final String name = groupNameInput.getEditableText().toString().trim(); + final String groupAddress = addressInput.getEditableText().toString().trim(); + if (validateInput(name, groupAddress)) { + if (mGroup != null) { + ((DialogFragmentPublicationListener) requireActivity()). + onPublishAddressSet(mGroup); + dismiss(); + } else { + if (((GroupCallbacks) requireActivity()) + .onGroupAdded(name, Integer.parseInt(groupAddress, 16))) { + dismiss(); + } + } + } + } else { + final Group group = (Group) groups.getSelectedItem(); + ((DialogFragmentPublicationListener) requireActivity()).onPublishAddressSet(group); + dismiss(); + } + } catch (IllegalArgumentException ex) { + addressInputLayout.setError(ex.getMessage()); + } + break; + case ALL_PROXIES: + ((DialogFragmentPublicationListener) requireActivity()).onPublishAddressSet(MeshAddress.ALL_PROXIES_ADDRESS); + dismiss(); + break; + case ALL_FRIENDS: + ((DialogFragmentPublicationListener) requireActivity()).onPublishAddressSet(MeshAddress.ALL_FRIENDS_ADDRESS); + dismiss(); + break; + case ALL_RELAYS: + ((DialogFragmentPublicationListener) requireActivity()).onPublishAddressSet(MeshAddress.ALL_RELAYS_ADDRESS); + dismiss(); + break; + case ALL_NODES: + ((DialogFragmentPublicationListener) requireActivity()).onPublishAddressSet(MeshAddress.ALL_NODES_ADDRESS); + dismiss(); + break; + case VIRTUAL_ADDRESS: + Group group = null; + try { + final UUID uuid = UUID.fromString(labelUuidView.getText().toString().trim()); + final String name = groupNameInput.getEditableText().toString().trim(); + group = ((GroupCallbacks) requireActivity()).createGroup(uuid, name); + if (group != null) { + if (((GroupCallbacks) requireActivity()).onGroupAdded(group)) { + dismiss(); + } + } + } catch (IllegalArgumentException ex) { + if (group != null) { + ((DialogFragmentPublicationListener) requireActivity()).onPublishAddressSet(group); + dismiss(); + } + } + break; + } + } + + private void setAddressType() { + int address = 0; + if (mPublicationSettings != null) { + address = mPublicationSettings.getPublishAddress(); + } + + mAdapterSpinner = new AddressTypeAdapter(requireContext(), addressTypes); + addressTypesSpinnerView.setAdapter(mAdapterSpinner); + final AddressType type = MeshAddress.getAddressType(address); + if (type != null) { + switch (type) { + default: + if (address == MeshAddress.ALL_PROXIES_ADDRESS) { + addressTypesSpinnerView.setSelection(2); + } else if (address == MeshAddress.ALL_FRIENDS_ADDRESS) { + addressTypesSpinnerView.setSelection(3); + } else if (address == MeshAddress.ALL_RELAYS_ADDRESS) { + addressTypesSpinnerView.setSelection(4); + } else { + addressTypesSpinnerView.setSelection(5); + } + break; + case UNICAST_ADDRESS: + addressTypesSpinnerView.setSelection(0); + break; + case GROUP_ADDRESS: + addressTypesSpinnerView.setSelection(1); + break; + case VIRTUAL_ADDRESS: + addressTypesSpinnerView.setSelection(addressTypes.length - 1); + break; + } + } + } + + private void updateAddress(@NonNull final AddressTypes addressType) { + int address = 0; + if (mPublicationSettings != null) { + address = mPublicationSettings.getPublishAddress(); + } + + switch (addressType) { + default: + case UNICAST_ADDRESS: + addressInput.getEditableText().clear(); + updateFixedGroupAddressVisibility(MeshAddress.ALL_PROXIES_ADDRESS, true); + break; + case GROUP_ADDRESS: + final int index = getGroupIndex(address); + groups.setSelection(index); + addressInputLayout.setEnabled(false); + groupNameInputLayout.setVisibility(View.VISIBLE); + groupContainer.setVisibility(View.VISIBLE); + labelContainer.setVisibility(View.GONE); + mGenerateLabelUUID.setVisibility(View.GONE); + final Group group = ((GroupCallbacks) requireActivity()) + .createGroup(groupNameInput.getEditableText().toString().trim()); + if (group != null) { + addressInput.setText(MeshAddress.formatAddress(group.getAddress(), false)); + } + break; + case ALL_PROXIES: + updateFixedGroupAddressVisibility(MeshAddress.ALL_PROXIES_ADDRESS, false); + break; + case ALL_FRIENDS: + updateFixedGroupAddressVisibility(MeshAddress.ALL_FRIENDS_ADDRESS, false); + break; + case ALL_RELAYS: + updateFixedGroupAddressVisibility(MeshAddress.ALL_RELAYS_ADDRESS, false); + break; + case ALL_NODES: + updateFixedGroupAddressVisibility(MeshAddress.ALL_NODES_ADDRESS, false); + break; + case VIRTUAL_ADDRESS: + if (mPublicationSettings != null && mPublicationSettings.getLabelUUID() != null) { + labelUuidView.setText(mPublicationSettings.getLabelUUID().toString().toUpperCase(Locale.US)); + } + labelContainer.setVisibility(View.VISIBLE); + mGenerateLabelUUID.setVisibility(View.VISIBLE); + addressInputLayout.setEnabled(false); + groupNameInputLayout.setVisibility(View.VISIBLE); + groupContainer.setVisibility(View.VISIBLE); + groupContainer.setVisibility(View.GONE); + generateVirtualAddress(UUID.fromString(labelUuidView.getText().toString())); + break; + } + } + + private void updateFixedGroupAddressVisibility(final int address, final boolean enabled) { + addressInput.setText(MeshAddress.formatAddress(address, false)); + addressInputLayout.setEnabled(enabled); + groupNameInputLayout.setVisibility(View.GONE); + groupContainer.setVisibility(View.GONE); + labelContainer.setVisibility(View.GONE); + mGenerateLabelUUID.setVisibility(View.GONE); + } + + private void generateVirtualAddress(@NonNull final UUID uuid) { + final Group group1 = ((GroupCallbacks) requireActivity()).createGroup(uuid, groupNameInput.getEditableText().toString().trim()); + if (group1 != null) { + addressInput.setText(MeshAddress.formatAddress(group1.getAddress(), false)); + } + } + + private int getGroupIndex(final int address) { + for (int i = 0; i < mGroups.size(); i++) { + if (address == mGroups.get(i).getAddress()) { + return i; + } + } + return 0; + } + + private boolean validateInput(@NonNull final String input) { + + try { + if (input.length() % 4 != 0 || !input.matches(Utils.HEX_PATTERN)) { + addressInputLayout.setError(getString(R.string.invalid_address_value)); + return false; + } + final AddressTypes type = (AddressTypes) addressTypesSpinnerView.getSelectedItem(); + + final int address = Integer.parseInt(input, 16); + switch (type) { + default: + if (!MeshAddress.isValidUnassignedAddress(address)) { + addressInputLayout.setError(getString(R.string.invalid_address_value)); + return false; + } + return true; + case UNICAST_ADDRESS: + if (!MeshAddress.isValidUnicastAddress(address)) { + addressInputLayout.setError(getString(R.string.invalid_unicast_address)); + return false; + } + return true; + case GROUP_ADDRESS: + if (!MeshAddress.isValidGroupAddress(address)) { + addressInputLayout.setError(getString(R.string.invalid_group_address)); + return false; + } + for (Group group : mGroups) { + if (address == group.getAddress()) { + addressInputLayout.setError(getString(R.string.error_group_address_in_used)); + return false; + } + } + return true; + case VIRTUAL_ADDRESS: + //do nothing since the library generates it + return true; + } + } catch (IllegalArgumentException ex) { + addressInputLayout.setError(ex.getMessage()); + return false; + } + } + + private boolean validateInput(@NonNull final String name, + @NonNull final String address) { + try { + if (TextUtils.isEmpty(name)) { + groupNameInputLayout.setError(getString(R.string.error_empty_group_name)); + return false; + } + if (address.length() % 4 != 0 || !address.matches(Utils.HEX_PATTERN)) { + addressInputLayout.setError(getString(R.string.invalid_address_value)); + return false; + } + + final int groupAddress = Integer.valueOf(address, 16); + if (!MeshAddress.isValidGroupAddress(groupAddress)) { + addressInputLayout.setError(getString(R.string.invalid_address_value)); + return false; + } + + for (Group group : mGroups) { + if (groupAddress == group.getAddress()) { + addressInputLayout.setError(getString(R.string.error_group_address_in_used)); + return false; + } + } + } catch (IllegalArgumentException ex) { + addressInputLayout.setError(ex.getMessage()); + return false; + } + + return true; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublishTtl.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublishTtl.java similarity index 86% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublishTtl.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublishTtl.java index e6d2b93d1..246752933 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentPublishTtl.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentPublishTtl.java @@ -20,17 +20,19 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; -import android.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; + +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import androidx.fragment.app.DialogFragment; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; @@ -76,7 +78,7 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_publish_ttl_input, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_publish_ttl_input, null); ButterKnife.bind(this, rootView); chkPublishTtl.setOnCheckedChangeListener((buttonView, isChecked) -> { @@ -95,7 +97,7 @@ public void beforeTextChanged(final CharSequence s, final int start, final int c @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { if (TextUtils.isEmpty(s.toString())) { - ttlInputLayout.setError(getString(R.string.error_empty_publish_ttl)); + ttlInputLayout.setError(getString(R.string.error_empty_ttl)); } else { ttlInputLayout.setError(null); } @@ -107,22 +109,21 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); alertDialogBuilder.setIcon(R.drawable.ic_timer); alertDialogBuilder.setTitle(R.string.title_publish_ttl); - alertDialogBuilder.setMessage(R.string.dialog_summary_publish_ttl); final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { if(chkPublishTtl.isChecked()) { - ((DialogFragmentPublishTtlListener) getActivity()).setPublishTtl(MeshParserUtils.USE_DEFAULT_TTL); + ((DialogFragmentPublishTtlListener) requireActivity()).setPublishTtl(MeshParserUtils.USE_DEFAULT_TTL); dismiss(); } else { - final String publishTtl = ttlInput.getText().toString(); + final String publishTtl = ttlInput.getEditableText().toString().trim(); if (validateInput(publishTtl)) { - ((DialogFragmentPublishTtlListener) getActivity()).setPublishTtl(Integer.parseInt(publishTtl)); + ((DialogFragmentPublishTtlListener) requireActivity()).setPublishTtl(Integer.parseInt(publishTtl)); dismiss(); } } @@ -148,7 +149,7 @@ public void afterTextChanged(final Editable s) { private boolean validateInput(final String input) { try { if(TextUtils.isEmpty(input)){ - ttlInputLayout.setError(getString(R.string.error_empty_publish_ttl)); + ttlInputLayout.setError(getString(R.string.error_empty_ttl)); return false; } if(!MeshParserUtils.isValidTtl(Integer.parseInt(input))) { diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentResetNode.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentResetNode.java similarity index 87% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentResetNode.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentResetNode.java index 64622ddad..e6204bda2 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentResetNode.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentResetNode.java @@ -20,14 +20,15 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentMessage; public class DialogFragmentResetNode extends DialogFragmentMessage { @@ -52,11 +53,11 @@ public void onCreate(Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - alertDialogBuilder = new AlertDialog.Builder(getActivity()); + alertDialogBuilder = new AlertDialog.Builder(requireActivity()); alertDialogBuilder.setIcon(R.drawable.ic_reset_black_24dp_alpha); alertDialogBuilder.setNegativeButton(getString(R.string.no), null); alertDialogBuilder.setPositiveButton(getString(R.string.yes), (dialog, which) -> ( - (DialogFragmentNodeResetListener)getActivity()).onNodeReset()); + (DialogFragmentNodeResetListener)requireActivity()).onNodeReset()); return super.onCreateDialog(savedInstanceState); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentRetransmitCount.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentRetransmitCount.java similarity index 88% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentRetransmitCount.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentRetransmitCount.java index aaa3f5290..2c1bf91f6 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentRetransmitCount.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogFragmentRetransmitCount.java @@ -20,17 +20,19 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; -import android.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; + +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import androidx.fragment.app.DialogFragment; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; @@ -75,7 +77,7 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_publication_parameters, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_publication_parameters, null); //Bind ui ButterKnife.bind(this, rootView); @@ -107,7 +109,7 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); alertDialogBuilder.setIcon(R.drawable.ic_numeric); @@ -115,10 +117,10 @@ public void afterTextChanged(final Editable s) { final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String ivIndexInput = this.retransmitInput.getText().toString(); + final String ivIndexInput = this.retransmitInput.getEditableText().toString().trim(); if (validateInput(ivIndexInput)) { if (getParentFragment() == null) { - ((DialogFragmentRetransmitCountListener) getActivity()).setRetransmitCount(Integer.parseInt(ivIndexInput, 16)); + ((DialogFragmentRetransmitCountListener) requireActivity()).setRetransmitCount(Integer.parseInt(ivIndexInput, 16)); } else { ((DialogFragmentRetransmitCountListener) getParentFragment()).setRetransmitCount(Integer.parseInt(ivIndexInput, 16)); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogRelayRetransmitSettings.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogRelayRetransmitSettings.java similarity index 87% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogRelayRetransmitSettings.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogRelayRetransmitSettings.java index 7e7dd813f..880e9a977 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogRelayRetransmitSettings.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/node/dialog/DialogRelayRetransmitSettings.java @@ -1,11 +1,12 @@ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.node.dialog; -import android.app.AlertDialog; +import android.annotation.SuppressLint; +import androidx.appcompat.app.AlertDialog; import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.widget.SeekBar; @@ -22,9 +23,9 @@ public class DialogRelayRetransmitSettings extends DialogFragment { private static final String TAG = DialogRelayRetransmitSettings.class.getSimpleName(); - public static final String RELAY = "RELAY"; - public static final String TRANSMIT_COUNT = "TRANSMIT_COUNT"; - public static final String TRANSMIT_INTERVAL_STEPS = "TRANSMIT_INTERVAL_STEPS"; + private static final String RELAY = "RELAY"; + private static final String TRANSMIT_COUNT = "TRANSMIT_COUNT"; + private static final String TRANSMIT_INTERVAL_STEPS = "TRANSMIT_INTERVAL_STEPS"; private static final int MIN_RETRANSMIT_COUNT = 0; private static final int MAX_RETRANSMIT_COUNT = 0b111; @@ -71,6 +72,7 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_fragment_relay_settings, null); ButterKnife.bind(this, rootView); @@ -118,7 +120,7 @@ public void onStopTrackingTouch(SeekBar seekBar) { } }); - return new AlertDialog.Builder(getContext()) + return new AlertDialog.Builder(requireContext()) .setView(rootView) .setPositiveButton(R.string.ok, (dialog, which) -> { if (getParentFragment() == null) { @@ -140,15 +142,15 @@ private void setRelay(final int relay) { private void setRelayRetransmitCount(final int relayRetransmitCount) { mTransmitCount = relayRetransmitCount; - relayRetransmitCountText.setText(getResources().getString( - R.string.text_network_transmit_count, relayRetransmitCount)); + relayRetransmitCountText.setText(getResources().getQuantityString( + R.plurals.transmit_count, relayRetransmitCount, relayRetransmitCount)); } private void setRelayRetransmitIntervalSteps(final int transmitIntervalSteps) { mTransmitIntervalSteps = transmitIntervalSteps; final int transmitIntervalMilliseconds = transmitIntervalSteps * 10; relayRetransmitIntervalStepsText.setText(getResources().getString( - R.string.text_network_transmit_interval_steps, transmitIntervalMilliseconds)); + R.string.time_ms, transmitIntervalMilliseconds)); } public interface DialogFragmentRelaySettingsListener { diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/AddProvisionerActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/AddProvisionerActivity.java new file mode 100644 index 000000000..e8a31468c --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/AddProvisionerActivity.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners; + +import android.bluetooth.BluetoothAdapter; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import no.nordicsemi.android.meshprovisioner.AllocatedGroupRange; +import no.nordicsemi.android.meshprovisioner.AllocatedSceneRange; +import no.nordicsemi.android.meshprovisioner.AllocatedUnicastRange; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.Provisioner; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentError; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentProvisionerAddress; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentProvisionerName; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentTtl; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentUnassign; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.AddProvisionerViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RangeView; + +public class AddProvisionerActivity extends AppCompatActivity implements Injectable, + DialogFragmentProvisionerName.DialogFragmentProvisionerNameListener, + DialogFragmentTtl.DialogFragmentTtlListener, + DialogFragmentProvisionerAddress.ProvisionerAddressListener, + DialogFragmentUnassign.DialogFragmentUnassignListener { + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + private TextView provisionerName; + private TextView provisionerUnicast; + private TextView provisionerTtl; + private RangeView unicastRangeView; + private RangeView groupRangeView; + private RangeView sceneRangeView; + + private AddProvisionerViewModel mViewModel; + private Provisioner mProvisioner; + + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_provisioner); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(AddProvisionerViewModel.class); + + //Bind ui + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_add_provisioner); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp); + + final View containerProvisionerName = findViewById(R.id.container_name); + containerProvisionerName.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_label_outline_black_alpha_24dp)); + ((TextView) containerProvisionerName.findViewById(R.id.title)).setText(R.string.name); + provisionerName = containerProvisionerName.findViewById(R.id.text); + provisionerName.setVisibility(View.VISIBLE); + + final View containerUnicast = findViewById(R.id.container_unicast); + containerUnicast.setClickable(false); + containerUnicast.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_index)); + ((TextView) containerUnicast.findViewById(R.id.title)).setText(R.string.title_unicast_address); + provisionerUnicast = containerUnicast.findViewById(R.id.text); + provisionerUnicast.setVisibility(View.VISIBLE); + + final View containerTtl = findViewById(R.id.container_ttl); + containerTtl.setClickable(false); + containerTtl.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_timer)); + ((TextView) containerTtl.findViewById(R.id.title)).setText(R.string.title_ttl); + provisionerTtl = containerTtl.findViewById(R.id.text); + provisionerTtl.setVisibility(View.VISIBLE); + + final CheckBox checkBox = findViewById(R.id.check_provisioner); + checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (mProvisioner != null) { + mProvisioner.setLastSelected(isChecked); + } + }); + + final View containerUnicastRange = findViewById(R.id.container_unicast_range); + containerUnicastRange.setClickable(false); + containerUnicastRange.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_lan_black_alpha_24dp)); + ((TextView) containerUnicastRange.findViewById(R.id.title)).setText(R.string.title_unicast_addresses); + unicastRangeView = containerUnicastRange.findViewById(R.id.range_view); + + final View containerGroupRange = findViewById(R.id.container_group_range); + containerGroupRange.setClickable(false); + containerGroupRange.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_outline_group_work_black_alpha_24dp)); + ((TextView) containerGroupRange.findViewById(R.id.title)).setText(R.string.title_group_addresses); + groupRangeView = containerGroupRange.findViewById(R.id.range_view); + + final View containerSceneRange = findViewById(R.id.container_scene_range); + containerSceneRange.setClickable(false); + containerSceneRange.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_arrow_collapse_black_alpha_24dp)); + ((TextView) containerSceneRange.findViewById(R.id.title)).setText(R.string.title_scenes); + sceneRangeView = containerSceneRange.findViewById(R.id.range_view); + + containerProvisionerName.setOnClickListener(v -> { + if (mProvisioner != null) { + final DialogFragmentProvisionerName fragment = DialogFragmentProvisionerName.newInstance(mProvisioner.getProvisionerName()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerUnicast.setOnClickListener(v -> { + if (mProvisioner != null) { + final DialogFragmentProvisionerAddress fragment = DialogFragmentProvisionerAddress.newInstance(mProvisioner.getProvisionerAddress()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerTtl.setOnClickListener(v -> { + if (mProvisioner != null) { + final DialogFragmentTtl fragment = DialogFragmentTtl.newInstance(mProvisioner.getGlobalTtl()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerUnicastRange.setOnClickListener(v -> { + if (mProvisioner != null) { + final Intent intent = new Intent(this, RangesActivity.class); + intent.putExtra(Utils.RANGE_TYPE, Utils.UNICAST_RANGE); + startActivity(intent); + } + }); + + containerGroupRange.setOnClickListener(v -> { + if (mProvisioner != null) { + final Intent intent = new Intent(this, RangesActivity.class); + intent.putExtra(Utils.RANGE_TYPE, Utils.GROUP_RANGE); + startActivity(intent); + } + }); + + containerSceneRange.setOnClickListener(v -> { + if (mProvisioner != null) { + final Intent intent = new Intent(this, RangesActivity.class); + intent.putExtra(Utils.RANGE_TYPE, Utils.SCENE_RANGE); + startActivity(intent); + } + }); + + if (savedInstanceState == null) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + final AllocatedUnicastRange unicastRange = network.nextAvailableUnicastAddressRange(0x199A); + final AllocatedGroupRange groupRange = network.nextAvailableGroupAddressRange(0x0C9A); + final AllocatedSceneRange sceneRange = network.nextAvailableSceneAddressRange(0x3334); + final Provisioner provisioner = network.createProvisioner("nRF Mesh Provisioner", unicastRange, groupRange, sceneRange); + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + provisioner.setProvisionerName(adapter.getName()); + mViewModel.setSelectedProvisioner(provisioner); + } + } + + mViewModel.getSelectedProvisioner().observe(this, provisioner -> { + if (provisioner != null) { + mProvisioner = provisioner; + updateUi(); + } + }); + + updateUi(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_save, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + case R.id.action_save: + if (save()) { + onBackPressed(); + } + return true; + } + return false; + } + + @Override + public boolean onNameChanged(@NonNull final String name) { + if (mProvisioner != null) { + mProvisioner.setProvisionerName(name); + updateUi(); + return true; + } + return false; + } + + @Override + public boolean setAddress(final int sourceAddress) { + if (mProvisioner != null) { + if (mProvisioner.assignProvisionerAddress(sourceAddress)) { + updateUi(); + return true; + } + } + return false; + } + + @Override + public void unassignProvisioner() { + if (mProvisioner != null) { + final DialogFragmentUnassign fragmentUnassign = DialogFragmentUnassign + .newInstance(getString(R.string.title_unassign_provisioner), getString(R.string.summary_unassign_provisioner)); + fragmentUnassign.show(getSupportFragmentManager(), null); + } + } + + @Override + public void onProvisionerUnassigned() { + if (mProvisioner != null) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + provisionerUnicast.setText(R.string.unicast_address_unassigned); + mProvisioner.assignProvisionerAddress(null); + network.disableConfigurationCapabilities(mProvisioner); + } + } + } + + @Override + public boolean setDefaultTtl(final int ttl) { + if (mProvisioner != null) { + mProvisioner.setGlobalTtl(ttl); + return true; + } + return false; + } + + private void updateUi() { + if (mProvisioner != null) { + provisionerName.setText(mProvisioner.getProvisionerName()); + if (mProvisioner.getProvisionerAddress() == null) { + provisionerUnicast.setText(R.string.not_assigned); + } else { + provisionerUnicast.setText(MeshAddress.formatAddress(mProvisioner.getProvisionerAddress(), true)); + } + + unicastRangeView.clearRanges(); + groupRangeView.clearRanges(); + sceneRangeView.clearRanges(); + + unicastRangeView.addRanges(mProvisioner.getAllocatedUnicastRanges()); + groupRangeView.addRanges(mProvisioner.getAllocatedGroupRanges()); + sceneRangeView.addRanges(mProvisioner.getAllocatedSceneRanges()); + + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + final String ttl = String.valueOf(mProvisioner.getGlobalTtl()); + provisionerTtl.setText(ttl); + unicastRangeView.clearOtherRanges(); + groupRangeView.clearOtherRanges(); + sceneRangeView.clearOtherRanges(); + for (Provisioner other : network.getProvisioners()) { + unicastRangeView.addOtherRanges(other.getAllocatedUnicastRanges()); + groupRangeView.addOtherRanges(other.getAllocatedGroupRanges()); + sceneRangeView.addOtherRanges(other.getAllocatedSceneRanges()); + } + } + } + } + + private boolean save() { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + try { + return network.addProvisioner(mProvisioner); + } catch (IllegalArgumentException ex) { + final DialogFragmentError fragment = DialogFragmentError. + newInstance(getString(R.string.title_error), ex.getMessage()); + fragment.show(getSupportFragmentManager(), null); + } + } + return false; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/EditProvisionerActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/EditProvisionerActivity.java new file mode 100644 index 000000000..87fd7b44b --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/EditProvisionerActivity.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners; + +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.Provisioner; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.dialog.DialogFragmentError; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentProvisionerAddress; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentProvisionerName; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentTtl; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentUnassign; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.EditProvisionerViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RangeView; + +public class EditProvisionerActivity extends AppCompatActivity implements Injectable, + DialogFragmentProvisionerName.DialogFragmentProvisionerNameListener, + DialogFragmentTtl.DialogFragmentTtlListener, + DialogFragmentProvisionerAddress.ProvisionerAddressListener, + DialogFragmentUnassign.DialogFragmentUnassignListener { + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + private TextView provisionerName; + private TextView provisionerUnicast; + private TextView provisionerTtl; + private RangeView unicastRangeView; + private RangeView groupRangeView; + private RangeView sceneRangeView; + private View selectProvisioner; + private CheckBox checkBox; + + + private EditProvisionerViewModel mViewModel; + private Provisioner mProvisioner; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_provisioner); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(EditProvisionerViewModel.class); + + //Bind ui + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_edit_provisioner); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + final View containerProvisionerName = findViewById(R.id.container_name); + containerProvisionerName.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_label_black_alpha_24dp)); + ((TextView) containerProvisionerName.findViewById(R.id.title)).setText(R.string.name); + provisionerName = containerProvisionerName.findViewById(R.id.text); + provisionerName.setVisibility(View.VISIBLE); + + final View containerUnicast = findViewById(R.id.container_unicast); + containerUnicast.setClickable(false); + containerUnicast.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_index)); + ((TextView) containerUnicast.findViewById(R.id.title)).setText(R.string.title_unicast_address); + provisionerUnicast = containerUnicast.findViewById(R.id.text); + provisionerUnicast.setVisibility(View.VISIBLE); + + final View containerTtl = findViewById(R.id.container_ttl); + containerTtl.setClickable(false); + containerTtl.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_timer)); + ((TextView) containerTtl.findViewById(R.id.title)).setText(R.string.title_ttl); + provisionerTtl = containerTtl.findViewById(R.id.text); + provisionerTtl.setVisibility(View.VISIBLE); + + selectProvisioner = findViewById(R.id.select_provisioner_container); + checkBox = findViewById(R.id.check_provisioner); + checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (mProvisioner != null) { + mProvisioner.setLastSelected(isChecked); + } + }); + + final View containerUnicastRange = findViewById(R.id.container_unicast_range); + containerUnicastRange.setClickable(false); + containerUnicastRange.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_lan_black_alpha_24dp)); + ((TextView) containerUnicastRange.findViewById(R.id.title)).setText(R.string.title_unicast_addresses); + unicastRangeView = containerUnicastRange.findViewById(R.id.range_view); + + final View containerGroupRange = findViewById(R.id.container_group_range); + containerGroupRange.setClickable(false); + containerGroupRange.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_outline_group_work_black_alpha_24dp)); + ((TextView) containerGroupRange.findViewById(R.id.title)).setText(R.string.title_group_addresses); + groupRangeView = containerGroupRange.findViewById(R.id.range_view); + + final View containerSceneRange = findViewById(R.id.container_scene_range); + containerSceneRange.setClickable(false); + containerSceneRange.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_scene_black_alpha_24dp)); + ((TextView) containerSceneRange.findViewById(R.id.title)).setText(R.string.title_scenes); + sceneRangeView = containerSceneRange.findViewById(R.id.range_view); + + containerProvisionerName.setOnClickListener(v -> { + if (mProvisioner != null) { + final DialogFragmentProvisionerName fragment = DialogFragmentProvisionerName.newInstance(mProvisioner.getProvisionerName()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerUnicast.setOnClickListener(v -> { + if (mProvisioner != null) { + final DialogFragmentProvisionerAddress fragment = DialogFragmentProvisionerAddress.newInstance(mProvisioner.getProvisionerAddress()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerTtl.setOnClickListener(v -> { + if (mProvisioner != null) { + final DialogFragmentTtl fragment = DialogFragmentTtl.newInstance(mProvisioner.getGlobalTtl()); + fragment.show(getSupportFragmentManager(), null); + } + }); + + containerUnicastRange.setOnClickListener(v -> { + if (mProvisioner != null) { + mViewModel.setSelectedProvisioner(mProvisioner); + final Intent intent = new Intent(this, RangesActivity.class); + intent.putExtra(Utils.RANGE_TYPE, Utils.UNICAST_RANGE); + startActivity(intent); + } + }); + + containerGroupRange.setOnClickListener(v -> { + if (mProvisioner != null) { + mViewModel.setSelectedProvisioner(mProvisioner); + final Intent intent = new Intent(this, RangesActivity.class); + intent.putExtra(Utils.RANGE_TYPE, Utils.GROUP_RANGE); + startActivity(intent); + } + }); + + containerSceneRange.setOnClickListener(v -> { + if (mProvisioner != null) { + mViewModel.setSelectedProvisioner(mProvisioner); + final Intent intent = new Intent(this, RangesActivity.class); + intent.putExtra(Utils.RANGE_TYPE, Utils.SCENE_RANGE); + startActivity(intent); + } + }); + + mViewModel.getSelectedProvisioner().observe(this, provisioner -> { + if (provisioner != null) { + mProvisioner = provisioner; + updateUi(provisioner); + } + }); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return false; + } + + @Override + public void onBackPressed() { + if (save()) { + super.onBackPressed(); + } + } + + @Override + public boolean onNameChanged(@NonNull final String name) { + if (mProvisioner != null) { + mProvisioner.setProvisionerName(name); + final Provisioner provisioner = mProvisioner; + if (save(provisioner)) { + provisionerName.setText(mProvisioner.getProvisionerName()); + return true; + } + } + return false; + } + + @Override + public boolean setAddress(final int sourceAddress) { + if (mProvisioner != null) { + if (mProvisioner.assignProvisionerAddress(sourceAddress)) { + final Provisioner provisioner = mProvisioner; + if (save(provisioner)) { + provisionerUnicast.setText(MeshAddress.formatAddress(sourceAddress, true)); + return true; + } + } + } + return false; + } + + @Override + public void unassignProvisioner() { + if (mProvisioner != null) { + final DialogFragmentUnassign fragmentUnassign = DialogFragmentUnassign + .newInstance(getString(R.string.title_unassign_provisioner), getString(R.string.summary_unassign_provisioner)); + fragmentUnassign.show(getSupportFragmentManager(), null); + } + } + + @Override + public void onProvisionerUnassigned() { + if (mProvisioner != null) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + provisionerUnicast.setText(R.string.unicast_address_unassigned); + mProvisioner.assignProvisionerAddress(null); + network.disableConfigurationCapabilities(mProvisioner); + } + } + } + + @Override + public boolean setDefaultTtl(final int ttl) { + if (mProvisioner != null) { + mProvisioner.setGlobalTtl(ttl); + final Provisioner provisioner = mProvisioner; + if (save(provisioner)) { + provisionerTtl.setText(String.valueOf(ttl)); + } + return true; + } + return false; + } + + private void updateUi(@NonNull final Provisioner provisioner) { + provisionerName.setText(provisioner.getProvisionerName()); + if (provisioner.getProvisionerAddress() == null) { + provisionerUnicast.setText(R.string.unicast_address_unassigned); + } else { + provisionerUnicast.setText(MeshAddress.formatAddress(provisioner.getProvisionerAddress(), true)); + } + if (provisioner.isLastSelected()) { + selectProvisioner.setVisibility(View.GONE); + } else { + checkBox.setChecked(provisioner.isLastSelected()); + } + unicastRangeView.clearRanges(); + groupRangeView.clearRanges(); + sceneRangeView.clearRanges(); + unicastRangeView.addRanges(provisioner.getAllocatedUnicastRanges()); + groupRangeView.addRanges(provisioner.getAllocatedGroupRanges()); + sceneRangeView.addRanges(provisioner.getAllocatedSceneRanges()); + + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + final String ttl = String.valueOf(provisioner.getGlobalTtl()); + provisionerTtl.setText(ttl); + + unicastRangeView.clearOtherRanges(); + groupRangeView.clearOtherRanges(); + sceneRangeView.clearOtherRanges(); + for (Provisioner other : network.getProvisioners()) { + if (!other.getProvisionerUuid().equalsIgnoreCase(provisioner.getProvisionerUuid())) { + unicastRangeView.addOtherRanges(other.getAllocatedUnicastRanges()); + groupRangeView.addOtherRanges(other.getAllocatedGroupRanges()); + sceneRangeView.addOtherRanges(other.getAllocatedSceneRanges()); + } + } + } + } + + private boolean save() { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + try { + return network.updateProvisioner(mProvisioner); + } catch (IllegalArgumentException ex) { + final DialogFragmentError fragment = DialogFragmentError. + newInstance(getString(R.string.title_error), ex.getMessage()); + fragment.show(getSupportFragmentManager(), null); + } + } + return false; + } + + private boolean save(@NonNull final Provisioner provisioner) { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + return network.updateProvisioner(provisioner); + } + return false; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/ProvisionersActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/ProvisionersActivity.java new file mode 100644 index 000000000..0bd522c00 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/ProvisionersActivity.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners; + +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.cardview.widget.CardView; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.Provisioner; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.adapter.ProvisionerAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.ProvisionersViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; + +public class ProvisionersActivity extends AppCompatActivity implements Injectable, + ProvisionerAdapter.OnItemClickListener, + ItemTouchHelperAdapter { + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + //UI Bindings + @BindView(R.id.toolbar) + Toolbar mToolbar; + @BindView(R.id.container) + CoordinatorLayout container; + @BindView(R.id.scroll_container) + ScrollView scrollView; + @BindView(R.id.provisioners_card) + CardView mProvisionersCard; + @BindView(R.id.recycler_view_provisioners) + RecyclerView mRecyclerView; + + private ProvisionersViewModel mViewModel; + private ProvisionerAdapter mAdapter; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_provisioners); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(ProvisionersViewModel.class); + + //Bind ui + ButterKnife.bind(this); + + setSupportActionBar(mToolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(R.string.title_manage_provisioners); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + final View containerProvisioner = findViewById(R.id.container_current_provisioner); + containerProvisioner.findViewById(R.id.image). + setBackground(ContextCompat.getDrawable(this, R.drawable.ic_account_key_black_alpha_24dp)); + final TextView provisionerTitle = containerProvisioner.findViewById(R.id.title); + final TextView provisionerView = containerProvisioner.findViewById(R.id.text); + provisionerView.setVisibility(View.VISIBLE); + + final ExtendedFloatingActionButton fab = findViewById(R.id.fab_add); + + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerView.setItemAnimator(null); + + final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); + final ItemTouchHelper mItemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); + mItemTouchHelper.attachToRecyclerView(mRecyclerView); + mAdapter = new ProvisionerAdapter(this, mViewModel.getNetworkLiveData()); + mAdapter.setOnItemClickListener(this); + mRecyclerView.setAdapter(mAdapter); + + mViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> { + final MeshNetwork network = meshNetworkLiveData.getMeshNetwork(); + if (network != null) { + final Provisioner provisioner = network.getSelectedProvisioner(); + provisionerTitle.setText(provisioner.getProvisionerName()); + if (provisioner.getProvisionerAddress() == null) { + provisionerView.setText(R.string.unicast_address_unassigned); + } else { + if (MeshAddress.isValidUnicastAddress(provisioner.getProvisionerAddress())) { + provisionerView.setText(getString(R.string.unicast_address, + MeshAddress.formatAddress(provisioner.getProvisionerAddress(), true))); + } else { + provisionerView.setText(R.string.unicast_address_unassigned); + } + } + + if (network.getProvisioners().size() > 1) { + mProvisionersCard.setVisibility(View.VISIBLE); + } else { + mProvisionersCard.setVisibility(View.GONE); + } + } + }); + + containerProvisioner.setOnClickListener(v -> { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + final Provisioner provisioner = network.getSelectedProvisioner(); + mViewModel.setSelectedProvisioner(provisioner); + final Intent intent = new Intent(this, EditProvisionerActivity.class); + startActivity(intent); + } + }); + + fab.setOnClickListener(v -> { + final Intent intent = new Intent(this, AddProvisionerActivity.class); + startActivity(intent); + }); + + scrollView.getViewTreeObserver().addOnScrollChangedListener(() -> { + if (scrollView.getScrollY() == 0) { + fab.extend(true); + } else { + fab.shrink(true); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return false; + } + + @Override + public void onItemClick(final int position, @NonNull final Provisioner provisioner) { + mViewModel.setSelectedProvisioner(provisioner); + final Intent intent = new Intent(this, EditProvisionerActivity.class); + startActivity(intent); + } + + @Override + public void onItemDismiss(final RemovableViewHolder viewHolder) { + final int position = viewHolder.getAdapterPosition(); + final Provisioner provisioner = mAdapter.getItem(position); + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + try { + if (network != null) { + if (network.removeProvisioner(provisioner)) { + displaySnackBar(provisioner); + } + } + } catch (Exception ex) { + mAdapter.notifyDataSetChanged(); + mViewModel.displaySnackBar(this, container, ex.getMessage(), Snackbar.LENGTH_LONG); + } + } + + @Override + public void onItemDismissFailed(final RemovableViewHolder viewHolder) { + + } + + private void displaySnackBar(@NonNull final Provisioner provisioner) { + Snackbar.make(container, getString(R.string.provisioner_deleted), Snackbar.LENGTH_LONG) + .setAction(getString(R.string.undo), view -> { + final MeshNetwork network = mViewModel.getNetworkLiveData().getMeshNetwork(); + if (network != null) { + network.addProvisioner(provisioner); + } + }) + .setActionTextColor(getResources().getColor(R.color.colorPrimaryDark)) + .show(); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/RangeListener.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/RangeListener.java new file mode 100644 index 000000000..1b4c33e55 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/RangeListener.java @@ -0,0 +1,10 @@ +package no.nordicsemi.android.nrfmeshprovisioner.provisioners; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.meshprovisioner.Range; + +public interface RangeListener { + + void addRange(@NonNull final Range range); + +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/RangesActivity.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/RangesActivity.java new file mode 100644 index 000000000..f76af0b72 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/RangesActivity.java @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners; + +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.AddressRange; +import no.nordicsemi.android.meshprovisioner.AllocatedGroupRange; +import no.nordicsemi.android.meshprovisioner.AllocatedSceneRange; +import no.nordicsemi.android.meshprovisioner.AllocatedUnicastRange; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.Provisioner; +import no.nordicsemi.android.meshprovisioner.Range; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.di.Injectable; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.adapter.RangeAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentGroupRange; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentSceneRange; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs.DialogFragmentUnicastRange; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.RangesViewModel; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.ItemTouchHelperAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RangeView; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableItemTouchHelperCallback; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; + +public class RangesActivity extends AppCompatActivity implements Injectable, + RangeAdapter.OnItemClickListener, + ItemTouchHelperAdapter, + RangeListener { + + @Inject + ViewModelProvider.Factory mViewModelFactory; + + //UI Bindings + @BindView(android.R.id.empty) + View mEmptyView; + @BindView(R.id.container) + CoordinatorLayout container; + @BindView(R.id.fab_resolve) + ExtendedFloatingActionButton mFabResolve; + private RangeView mRangeView; + private int mType; + private RangesViewModel mViewModel; + private RangeAdapter mRangeAdapter; + private Provisioner mProvisioner; + + private final Comparator addressRangeComparator = (addressRange1, addressRange2) -> + Integer.compare(addressRange1.getLowAddress(), addressRange2.getLowAddress()); + + private final Comparator sceneRangeComparator = (sceneRange1, sceneRange2) -> + Integer.compare(sceneRange1.getFirstScene(), sceneRange2.getFirstScene()); + + @SuppressWarnings("ConstantConditions") + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ranges); + ButterKnife.bind(this); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(RangesViewModel.class); + mType = getIntent().getExtras().getInt(Utils.RANGE_TYPE); + + //Bind ui + final Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + final RecyclerView recyclerView = findViewById(R.id.recycler_view); + final View rangesContainer = findViewById(R.id.info_ranges); + rangesContainer.findViewById(R.id.container).setVisibility(View.VISIBLE); + mRangeView = rangesContainer.findViewById(R.id.range); + final TextView startAddress = rangesContainer.findViewById(R.id.start_address); + final TextView endAddress = rangesContainer.findViewById(R.id.end_address); + final ExtendedFloatingActionButton fab_add = findViewById(R.id.fab_add); + + mProvisioner = mViewModel.getSelectedProvisioner().getValue(); + + switch (mType) { + case Utils.GROUP_RANGE: + startAddress.setText(MeshAddress.formatAddress(MeshAddress.START_GROUP_ADDRESS, true)); + endAddress.setText(MeshAddress.formatAddress(MeshAddress.END_GROUP_ADDRESS, true)); + getSupportActionBar().setTitle(R.string.title_edit_group_ranges); + mRangeAdapter = new RangeAdapter(this, + mProvisioner.getProvisionerUuid(), + mProvisioner.getAllocatedGroupRanges(), + mViewModel.getNetworkLiveData().getProvisioners()); + break; + case Utils.SCENE_RANGE: + startAddress.setText(MeshAddress.formatAddress(0x0000, true)); + endAddress.setText(MeshAddress.formatAddress(0xFFFF, true)); + getSupportActionBar().setTitle(R.string.title_edit_scene_ranges); + mRangeAdapter = new RangeAdapter(this, + mProvisioner.getProvisionerUuid(), + mProvisioner.getAllocatedSceneRanges(), + mViewModel.getNetworkLiveData().getProvisioners()); + break; + default: + case Utils.UNICAST_RANGE: + startAddress.setText(MeshAddress.formatAddress(MeshAddress.START_UNICAST_ADDRESS, true)); + endAddress.setText(MeshAddress.formatAddress(MeshAddress.END_UNICAST_ADDRESS, true)); + getSupportActionBar().setTitle(R.string.title_edit_unicast_ranges); + mRangeAdapter = new RangeAdapter(this, + mProvisioner.getProvisionerUuid(), + mProvisioner.getAllocatedUnicastRanges(), + mViewModel.getNetworkLiveData().getProvisioners()); + break; + } + + mRangeAdapter.setOnItemClickListener(this); + + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + final DividerItemDecoration dividerItemDecoration = + new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL); + recyclerView.addItemDecoration(dividerItemDecoration); + recyclerView.setItemAnimator(new DefaultItemAnimator()); + recyclerView.setAdapter(mRangeAdapter); + final ItemTouchHelper.Callback itemTouchHelperCallback = new RemovableItemTouchHelperCallback(this); + final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback); + itemTouchHelper.attachToRecyclerView(recyclerView); + + fab_add.setOnClickListener(v -> { + switch (mType) { + case Utils.GROUP_RANGE: + final DialogFragmentGroupRange groupRange = DialogFragmentGroupRange.newInstance(null); + groupRange.show(getSupportFragmentManager(), null); + break; + case Utils.SCENE_RANGE: + final DialogFragmentSceneRange sceneRange = DialogFragmentSceneRange.newInstance(null); + sceneRange.show(getSupportFragmentManager(), null); + break; + default: + case Utils.UNICAST_RANGE: + final DialogFragmentUnicastRange unicastRange = DialogFragmentUnicastRange.newInstance(null); + unicastRange.show(getSupportFragmentManager(), null); + break; + } + }); + + mFabResolve.setOnClickListener(v -> resolveRanges()); + + updateRanges(); + updateOtherRanges(); + updateEmptyView(); + updateResolveFab(); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return false; + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + mViewModel.setSelectedProvisioner(mProvisioner); + } + + @Override + public void onItemClick(final int position, @NonNull final Range range) { + if (range instanceof AllocatedUnicastRange) { + final DialogFragmentUnicastRange fragment = DialogFragmentUnicastRange.newInstance((AllocatedUnicastRange) range); + fragment.show(getSupportFragmentManager(), null); + } else if (range instanceof AllocatedGroupRange) { + final DialogFragmentGroupRange fragment = DialogFragmentGroupRange.newInstance((AllocatedGroupRange) range); + fragment.show(getSupportFragmentManager(), null); + } else { + final DialogFragmentSceneRange fragment = DialogFragmentSceneRange.newInstance((AllocatedSceneRange) range); + fragment.show(getSupportFragmentManager(), null); + } + } + + @Override + public void addRange(@NonNull final Range range) { + if (mProvisioner != null) { + mProvisioner.addRange(range); + updateData(range); + updateRanges(); + updateOtherRanges(); + updateEmptyView(); + updateResolveFab(); + } + } + + @Override + public void onItemDismiss(final RemovableViewHolder viewHolder) { + if (mProvisioner != null) { + final int position = viewHolder.getAdapterPosition(); + try { + final Range range = mRangeAdapter.getItem(position); + mRangeAdapter.removeItem(position); + displaySnackBar(position, range); + mProvisioner.removeRange(range); + updateRanges(); + updateOtherRanges(); + updateEmptyView(); + updateResolveFab(); + } catch (Exception ex) { + mRangeAdapter.notifyDataSetChanged(); + mViewModel.displaySnackBar(this, container, ex.getMessage(), Snackbar.LENGTH_LONG); + } + } + } + + @Override + public void onItemDismissFailed(final RemovableViewHolder viewHolder) { + + } + + private void updateRanges() { + if (mProvisioner != null) { + mRangeView.clearRanges(); + switch (mType) { + case Utils.GROUP_RANGE: + mRangeView.addRanges(mProvisioner.getAllocatedGroupRanges()); + break; + case Utils.SCENE_RANGE: + mRangeView.addRanges(mProvisioner.getAllocatedSceneRanges()); + break; + default: + case Utils.UNICAST_RANGE: + mRangeView.addRanges(mProvisioner.getAllocatedUnicastRanges()); + break; + } + } + } + + private void updateOtherRanges() { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + mRangeView.clearOtherRanges(); + for (Provisioner other : network.getProvisioners()) { + if (!other.getProvisionerUuid().equalsIgnoreCase(mProvisioner.getProvisionerUuid())) + switch (mType) { + case Utils.GROUP_RANGE: + mRangeView.addOtherRanges(other.getAllocatedGroupRanges()); + break; + case Utils.SCENE_RANGE: + mRangeView.addOtherRanges(other.getAllocatedSceneRanges()); + break; + default: + case Utils.UNICAST_RANGE: + mRangeView.addOtherRanges(other.getAllocatedUnicastRanges()); + break; + } + } + } + } + + private void updateResolveFab() { + final MeshNetwork network = mViewModel.getMeshManagerApi().getMeshNetwork(); + if (network != null) { + for (Provisioner other : network.getProvisioners()) { + if (!other.getProvisionerUuid().equalsIgnoreCase(mProvisioner.getProvisionerUuid())) + switch (mType) { + case Utils.GROUP_RANGE: + if (mProvisioner.hasOverlappingGroupRanges(other.getAllocatedGroupRanges())) { + mFabResolve.show(true); + return; + } else { + mFabResolve.hide(true); + } + break; + case Utils.SCENE_RANGE: + if (mProvisioner.hasOverlappingSceneRanges(other.getAllocatedSceneRanges())) { + mFabResolve.show(true); + return; + } else { + mFabResolve.hide(true); + } + break; + default: + case Utils.UNICAST_RANGE: + if (mProvisioner.hasOverlappingUnicastRanges(other.getAllocatedUnicastRanges())) { + mFabResolve.show(true); + return; + } else { + mFabResolve.hide(true); + } + break; + } + } + } + } + + private void updateEmptyView() { + // Show the empty view + if (mRangeAdapter.isEmpty()) { + mEmptyView.setVisibility(View.VISIBLE); + } else { + mEmptyView.setVisibility(View.GONE); + } + } + + private void updateData(@NonNull final Range range) { + if (mProvisioner != null) { + if (range instanceof AllocatedUnicastRange) { + mRangeAdapter.updateData(mProvisioner.getAllocatedUnicastRanges()); + } else if (range instanceof AllocatedGroupRange) { + mRangeAdapter.updateData(mProvisioner.getAllocatedGroupRanges()); + } else { + mRangeAdapter.updateData(mProvisioner.getAllocatedSceneRanges()); + } + } + } + + private void displaySnackBar(final int position, final Range range) { + Snackbar.make(container, getString(R.string.range_deleted), Snackbar.LENGTH_LONG) + .setAction(getString(R.string.undo), view -> { + mRangeAdapter.addItem(position, range); + mProvisioner.addRange(range); + updateRanges(); + updateOtherRanges(); + updateEmptyView(); + updateResolveFab(); + }) + .setActionTextColor(getResources().getColor(R.color.colorPrimaryDark)) + .show(); + } + + private void resolveRanges() { + switch (mType) { + case Utils.GROUP_RANGE: + removeConflictingGroupRanges(); + break; + case Utils.SCENE_RANGE: + removeConflictingSceneRanges(); + break; + default: + case Utils.UNICAST_RANGE: + removeConflictingUnicastRanges(); + break; + } + } + + private void removeConflictingUnicastRanges() { + if (mProvisioner != null) { + List ranges = new ArrayList<>(mProvisioner.getAllocatedUnicastRanges()); + Collections.sort(ranges, addressRangeComparator); + for (Provisioner p : mViewModel.getNetworkLiveData().getProvisioners()) { + if(!p.getProvisionerUuid().equalsIgnoreCase(mProvisioner.getProvisionerUuid())) { + final List otherRanges = new ArrayList<>(p.getAllocatedUnicastRanges()); + Collections.sort(otherRanges, addressRangeComparator); + for (AllocatedUnicastRange otherRange : otherRanges) { + ranges = AddressRange.minus(ranges, otherRange); + } + } + } + mProvisioner.setAllocatedUnicastRanges(ranges); + mRangeAdapter.updateData(ranges); + updateRanges(); + updateOtherRanges(); + updateEmptyView(); + updateResolveFab(); + } + } + + private void removeConflictingGroupRanges() { + if (mProvisioner != null) { + List ranges = new ArrayList<>(mProvisioner.getAllocatedGroupRanges()); + Collections.sort(ranges, addressRangeComparator); + for (Provisioner p : mViewModel.getNetworkLiveData().getProvisioners()) { + final List otherRanges = new ArrayList<>(p.getAllocatedGroupRanges()); + Collections.sort(otherRanges, addressRangeComparator); + for (AllocatedGroupRange otherRange : otherRanges) { + ranges = AddressRange.minus(ranges, otherRange); + } + } + + Collections.sort(ranges, addressRangeComparator); + mProvisioner.setAllocatedGroupRanges(ranges); + mRangeAdapter.updateData(ranges); + updateRanges(); + updateOtherRanges(); + updateEmptyView(); + updateResolveFab(); + } + } + + private void removeConflictingSceneRanges() { + if (mProvisioner != null) { + List ranges = new ArrayList<>(mProvisioner.getAllocatedSceneRanges()); + Collections.sort(ranges, sceneRangeComparator); + for (Provisioner p : mViewModel.getNetworkLiveData().getProvisioners()) { + final List otherRanges = new ArrayList<>(p.getAllocatedSceneRanges()); + Collections.sort(otherRanges, sceneRangeComparator); + for (AllocatedSceneRange otherRange : otherRanges) { + ranges = AllocatedSceneRange.minus(ranges, otherRange); + } + } + + Collections.sort(ranges, sceneRangeComparator); + mProvisioner.setAllocatedSceneRanges(ranges); + mRangeAdapter.updateData(ranges); + updateRanges(); + updateOtherRanges(); + updateEmptyView(); + updateResolveFab(); + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/adapter/ProvisionerAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/adapter/ProvisionerAdapter.java new file mode 100644 index 000000000..bce5980bb --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/adapter/ProvisionerAdapter.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.LifecycleOwner; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; +import no.nordicsemi.android.meshprovisioner.Provisioner; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.viewmodels.MeshNetworkLiveData; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; + +public class ProvisionerAdapter extends RecyclerView.Adapter { + + private final List mProvisioners = new ArrayList<>(); + private final Context mContext; + private OnItemClickListener mOnItemClickListener; + + public ProvisionerAdapter(@NonNull final Context context, @NonNull final MeshNetworkLiveData meshNetworkLiveData) { + this.mContext = context; + meshNetworkLiveData.observe((LifecycleOwner) context, networkData -> { + final MeshNetwork network = meshNetworkLiveData.getMeshNetwork(); + final List provisioners = network.getProvisioners(); + mProvisioners.clear(); + mProvisioners.addAll(provisioners); + final Provisioner provisioner = network.getSelectedProvisioner(); + mProvisioners.remove(provisioner); + notifyDataSetChanged(); + }); + } + + public void setOnItemClickListener(final ProvisionerAdapter.OnItemClickListener listener) { + mOnItemClickListener = listener; + } + + @NonNull + @Override + public ProvisionerAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.removable_row_item1, parent, false); + return new ProvisionerAdapter.ViewHolder(layoutView); + } + + @Override + public void onBindViewHolder(@NonNull final ProvisionerAdapter.ViewHolder holder, final int position) { + final Provisioner provisioner = mProvisioners.get(position); + holder.provisionerName.setText(provisioner.getProvisionerName()); + if (provisioner.getProvisionerAddress() == null) { + holder.provisionerSummary.setText(mContext.getString(R.string.unicast_address, + mContext.getString(R.string.address_unassigned))); + } else { + holder.provisionerSummary.setText(mContext.getString(R.string.unicast_address, + MeshAddress.formatAddress(provisioner.getProvisionerAddress(), true))); + } + } + + @Override + public long getItemId(final int position) { + return position; + } + + @Override + public int getItemCount() { + return mProvisioners.size(); + } + + public boolean isEmpty() { + return getItemCount() == 0; + } + + public Provisioner getItem(final int position) { + return mProvisioners.get(position); + } + + @FunctionalInterface + public interface OnItemClickListener { + void onItemClick(final int position, @NonNull final Provisioner provisioner); + } + + final class ViewHolder extends RemovableViewHolder { + + @BindView(R.id.icon) + ImageView icon; + @BindView(R.id.title) + TextView provisionerName; + @BindView(R.id.subtitle) + TextView provisionerSummary; + + private ViewHolder(final View view) { + super(view); + ButterKnife.bind(this, view); + icon.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_account_key_black_alpha_24dp)); + + view.findViewById(R.id.removable).setOnClickListener(v -> { + if (mOnItemClickListener != null) { + final Provisioner provisioner = mProvisioners.get(getAdapterPosition()); + mOnItemClickListener.onItemClick(getAdapterPosition(), provisioner); + } + }); + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/adapter/RangeAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/adapter/RangeAdapter.java new file mode 100644 index 000000000..81f485dd7 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/adapter/RangeAdapter.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.AddressRange; +import no.nordicsemi.android.meshprovisioner.AllocatedGroupRange; +import no.nordicsemi.android.meshprovisioner.AllocatedSceneRange; +import no.nordicsemi.android.meshprovisioner.AllocatedUnicastRange; +import no.nordicsemi.android.meshprovisioner.Provisioner; +import no.nordicsemi.android.meshprovisioner.Range; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RangeView; +import no.nordicsemi.android.nrfmeshprovisioner.widgets.RemovableViewHolder; + +public class RangeAdapter extends RecyclerView.Adapter { + + private final ArrayList mRanges; + private final List mProvisioners; + private final Context mContext; + private final String mUuid; + private OnItemClickListener mOnItemClickListener; + + public RangeAdapter(@NonNull final Context context, @NonNull final String uuid, @NonNull final List ranges, @NonNull final List provisioners) { + mContext = context; + mUuid = uuid; + mRanges = new ArrayList<>(ranges); + mProvisioners = provisioners; + } + + public void setOnItemClickListener(final RangeAdapter.OnItemClickListener listener) { + mOnItemClickListener = listener; + } + + public void updateData(@NonNull List ranges) { + mRanges.clear(); + mRanges.addAll(ranges); + notifyDataSetChanged(); + } + + @NonNull + @Override + public RangeAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final View layoutView = LayoutInflater.from(mContext).inflate(R.layout.range_item, parent, false); + return new RangeAdapter.ViewHolder(layoutView); + } + + @Override + public void onBindViewHolder(@NonNull final RangeAdapter.ViewHolder holder, final int position) { + final Range range = mRanges.get(position); + final String low, high; + if (range instanceof AddressRange) { + low = MeshAddress.formatAddress(((AddressRange) range).getLowAddress(), true); + high = MeshAddress.formatAddress(((AddressRange) range).getHighAddress(), true); + } else { + low = MeshAddress.formatAddress(((AllocatedSceneRange) range).getFirstScene(), true); + high = MeshAddress.formatAddress(((AllocatedSceneRange) range).getLastScene(), true); + } + holder.rangeValue.setText(mContext.getString(R.string.range_adapter_format, low, high)); + holder.rangeView.clearRanges(); + holder.rangeView.addRange(range); + addOverlappingRanges(range, holder.rangeView); + } + + @Override + public long getItemId(final int position) { + return position; + } + + @Override + public int getItemCount() { + return mRanges.size(); + } + + public boolean isEmpty() { + return getItemCount() == 0; + } + + public Range getItem(final int position) { + return mRanges.get(position); + } + + public List getItems() { + return mRanges; + } + + public void addItem(final int position, @NonNull final Range range) { + mRanges.add(position, range); + notifyItemInserted(position); + } + + public void removeItem(final int position) { + mRanges.remove(position); + notifyItemRemoved(position); + } + + private void addOverlappingRanges(@NonNull final Range range, @NonNull final RangeView rangeView) { + rangeView.clearOtherRanges(); + for (Provisioner p : mProvisioners) { + if (!p.getProvisionerUuid().equalsIgnoreCase(mUuid)) { + if (range instanceof AllocatedUnicastRange) { + for (AllocatedUnicastRange otherRange : p.getAllocatedUnicastRanges()) { + if (range.overlaps(otherRange)) { + rangeView.addOtherRange(otherRange); + } + } + } else if (range instanceof AllocatedGroupRange) { + for (AllocatedGroupRange otherRange : p.getAllocatedGroupRanges()) { + if (range.overlaps(otherRange)) { + rangeView.addOtherRange(otherRange); + } + } + } else { + for (AllocatedSceneRange otherRange : p.getAllocatedSceneRanges()) { + if (range.overlaps(otherRange)) { + rangeView.addOtherRange(otherRange); + } + } + } + } + } + } + + @FunctionalInterface + public interface OnItemClickListener { + void onItemClick(final int position, @NonNull final Range range); + } + + final class ViewHolder extends RemovableViewHolder { + + @BindView(R.id.range_text) + TextView rangeValue; + @BindView(R.id.range) + RangeView rangeView; + + private ViewHolder(final View view) { + super(view); + ButterKnife.bind(this, view); + view.findViewById(R.id.removable).setOnClickListener(v -> { + if (mOnItemClickListener != null) { + mOnItemClickListener.onItemClick(getAdapterPosition(), mRanges.get(getAdapterPosition())); + } + }); + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentGroupRange.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentGroupRange.java new file mode 100644 index 000000000..45640cc81 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentGroupRange.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.KeyListener; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import org.w3c.dom.Text; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.AllocatedGroupRange; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.RangeListener; +import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; + +public class DialogFragmentGroupRange extends DialogFragment { + + private static final String RANGE = "RANGE"; + //UI Bindings + @BindView(R.id.low_address_layout) + TextInputLayout lowAddressInputLayout; + @BindView(R.id.low_address_input) + TextInputEditText lowAddressInput; + @BindView(R.id.high_address_layout) + TextInputLayout highAddressInputLayout; + @BindView(R.id.high_address_input) + TextInputEditText highAddressInput; + + private AllocatedGroupRange mRange; + + public static DialogFragmentGroupRange newInstance(@Nullable final AllocatedGroupRange range) { + DialogFragmentGroupRange fragment = new DialogFragmentGroupRange(); + final Bundle args = new Bundle(); + args.putParcelable(RANGE, range); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mRange = getArguments().getParcelable(RANGE); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_range, null); + + //Bind ui + ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); + if (mRange != null) { + final String lowAddress = MeshAddress.formatAddress(mRange.getLowAddress(), false); + final String highAddress = MeshAddress.formatAddress(mRange.getHighAddress(), false); + lowAddressInput.setText(lowAddress); + lowAddressInput.setSelection(lowAddress.length()); + highAddressInput.setText(highAddress); + highAddressInput.setSelection(highAddress.length()); + } + + final KeyListener hexKeyListener = new HexKeyListener(); + lowAddressInput.setKeyListener(hexKeyListener); + lowAddressInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + if (TextUtils.isEmpty(s.toString())) { + lowAddressInputLayout.setError(getString(R.string.error_empty_value)); + } else { + lowAddressInputLayout.setError(null); + } + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }); + + highAddressInput.setKeyListener(hexKeyListener); + highAddressInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + if (TextUtils.isEmpty(s.toString())) { + highAddressInputLayout.setError(getString(R.string.error_empty_value)); + } else { + highAddressInputLayout.setError(null); + } + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }); + + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()) + .setView(rootView) + .setIcon(R.drawable.ic_arrow_collapse_black_alpha_24dp) + .setTitle(R.string.title_range) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null); + + summary.setText(R.string.group_range_summary); + + final AlertDialog alertDialog = alertDialogBuilder.show(); + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { + final String low = lowAddressInput.getEditableText().toString().trim(); + final String high = highAddressInput.getEditableText().toString().trim(); + if (validateLow(low) && validateHigh(high)) { + try { + AllocatedGroupRange range = mRange; + if (range == null) { + range = new AllocatedGroupRange(Integer.parseInt(low, 16), Integer.parseInt(high, 16)); + } else { + range.setLowAddress(Integer.parseInt(low, 16)); + range.setHighAddress(Integer.parseInt(high, 16)); + } + ((RangeListener) requireActivity()).addRange(range); + dismiss(); + } catch (IllegalArgumentException ex) { + lowAddressInputLayout.setError(ex.getMessage()); + } + } + }); + + return alertDialog; + } + + private boolean validateLow(@NonNull final String addressValue) { + try { + + if(TextUtils.isEmpty(addressValue)){ + lowAddressInputLayout.setError(getString(R.string.error_empty_value)); + return false; + } + + final int address = Integer.parseInt(addressValue, 16); + if (!MeshAddress.isValidGroupAddress(address)) { + lowAddressInputLayout.setError("Group address value must range from 0xC000 - 0xFEFF"); + return false; + } + } catch (IllegalArgumentException ex) { + lowAddressInputLayout.setError(ex.getMessage()); + return false; + } + return true; + } + + private boolean validateHigh(final String addressValue) { + try { + + if(TextUtils.isEmpty(addressValue)){ + highAddressInputLayout.setError("High address value cannot be empty"); + return false; + } + + final int address = Integer.parseInt(addressValue, 16); + if (!MeshAddress.isValidGroupAddress(address)) { + highAddressInputLayout.setError("Group address value must range from 0xC000 - 0xFEFF"); + return false; + } + } catch (IllegalArgumentException ex) { + highAddressInputLayout.setError(ex.getMessage()); + return false; + } + return true; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentSourceAddress.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentProvisionerAddress.java similarity index 61% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentSourceAddress.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentProvisionerAddress.java index 8e529b5f3..39cbe6e56 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentSourceAddress.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentProvisionerAddress.java @@ -20,26 +20,29 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.KeyListener; import android.view.LayoutInflater; import android.view.View; +import android.widget.Button; +import android.widget.TextView; -import java.util.Locale; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; @@ -48,7 +51,7 @@ import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; -public class DialogFragmentSourceAddress extends DialogFragment { +public class DialogFragmentProvisionerAddress extends DialogFragment { private static final String UNICAST_ADDRESS = "UNICAST_ADDRESS"; //UI Bindings @@ -57,13 +60,15 @@ public class DialogFragmentSourceAddress extends DialogFragment { @BindView(R.id.text_input) TextInputEditText unicastAddressInput; - private int mUnicastAddress; + private Integer mUnicastAddress = null; - public static DialogFragmentSourceAddress newInstance(final int unicastAddress) { - DialogFragmentSourceAddress fragmentIvIndex = new DialogFragmentSourceAddress(); - final Bundle args = new Bundle(); - args.putInt(UNICAST_ADDRESS, unicastAddress); - fragmentIvIndex.setArguments(args); + public static DialogFragmentProvisionerAddress newInstance(@Nullable final Integer unicastAddress) { + final DialogFragmentProvisionerAddress fragmentIvIndex = new DialogFragmentProvisionerAddress(); + if (unicastAddress != null) { + final Bundle args = new Bundle(); + args.putInt(UNICAST_ADDRESS, unicastAddress); + fragmentIvIndex.setArguments(args); + } return fragmentIvIndex; } @@ -78,16 +83,19 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_address_input, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_address_input, null); //Bind ui ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); final KeyListener hexKeyListener = new HexKeyListener(); - final String unicastAddress = String.format(Locale.US, "%04X", mUnicastAddress); - unicastAddressInputLayout.setHint(getString((R.string.hint_src_address))); - unicastAddressInput.setText(unicastAddress); - unicastAddressInput.setSelection(unicastAddress.length()); + unicastAddressInputLayout.setHint(getString((R.string.hint_unicast_address))); + if (mUnicastAddress != null && MeshAddress.isValidUnicastAddress(mUnicastAddress)) { + final String unicastAddress = MeshAddress.formatAddress(mUnicastAddress, false); + unicastAddressInput.setText(unicastAddress); + unicastAddressInput.setSelection(unicastAddress.length()); + } unicastAddressInput.setKeyListener(hexKeyListener); unicastAddressInput.addTextChangedListener(new TextWatcher() { @Override @@ -110,61 +118,57 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) - .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()) + .setView(rootView) + .setIcon(R.drawable.ic_lan_black_alpha_24dp) + .setTitle(R.string.title_provisioner_address) + .setPositiveButton(R.string.ok, null) + .setNeutralButton(R.string.action_unassign, null) + .setNegativeButton(R.string.cancel, null); - alertDialogBuilder.setIcon(R.drawable.ic_lan_black_alpha_24dp); - alertDialogBuilder.setTitle(R.string.title_src_address); - alertDialogBuilder.setMessage(R.string.dialog_summary_src); + summary.setText(R.string.dialog_summary_src); final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String unicast = unicastAddressInput.getText().toString(); - if (validateInput(unicast)) { + final String unicast = unicastAddressInput.getEditableText().toString().trim(); + if (validateInput(unicast)) try { - if (getParentFragment() == null) { - if(((DialogFragmentSourceAddressListener) getActivity()).setSourceAddress(Integer.parseInt(unicast, 16))){ - dismiss(); - } - } else { - if(((DialogFragmentSourceAddressListener) getParentFragment()).setSourceAddress(Integer.parseInt(unicast, 16))){ - dismiss(); - } + if (((ProvisionerAddressListener) requireActivity()).setAddress(Integer.parseInt(unicast, 16))) { + dismiss(); } } catch (IllegalArgumentException ex) { unicastAddressInputLayout.setError(ex.getMessage()); } - } + }); + + final Button unassign = alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL); + unassign.setTextColor(ContextCompat.getColor(requireContext(), R.color.nordicRed)); + unassign.setOnClickListener(v -> { + dismiss(); + ((ProvisionerAddressListener) requireActivity()).unassignProvisioner(); }); return alertDialog; } private boolean validateInput(final String input) { - try { - - if(input.length() % 4 != 0 || !input.matches(Utils.HEX_PATTERN)) { + if (input.length() % 4 != 0 || !input.matches(Utils.HEX_PATTERN)) { unicastAddressInputLayout.setError(getString(R.string.invalid_address_value)); return false; } - - final int unicastAddress = Integer.parseInt(input, 16); - if(!MeshAddress.isValidUnicastAddress(unicastAddress)) { - unicastAddressInputLayout.setError("Unicast address must range from 0x0001 - 0x7FFFF"); - return false; - } } catch (IllegalArgumentException ex) { unicastAddressInputLayout.setError(ex.getMessage()); + return false; } - return true; } - public interface DialogFragmentSourceAddressListener { + public interface ProvisionerAddressListener { - boolean setSourceAddress(final int sourceAddress); + boolean setAddress(final int sourceAddress); + void unassignProvisioner(); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentProvisionerName.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentProvisionerName.java new file mode 100644 index 000000000..7780aac92 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentProvisionerName.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.nrfmeshprovisioner.R; + +public class DialogFragmentProvisionerName extends DialogFragment { + + private static final String PROVISIONER_NAME = "PROVISIONER_NAME"; + + //UI Bindings + @BindView(R.id.text_input_layout) + TextInputLayout nameInputLayout; + @BindView(R.id.text_input) + TextInputEditText nameInput; + + private String mProvisionerName; + + public static DialogFragmentProvisionerName newInstance(final String name) { + DialogFragmentProvisionerName fragmentNetworkKey = new DialogFragmentProvisionerName(); + final Bundle args = new Bundle(); + args.putString(PROVISIONER_NAME, name); + fragmentNetworkKey.setArguments(args); + return fragmentNetworkKey; + } + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mProvisionerName = getArguments().getString(PROVISIONER_NAME); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_name, null); + + //Bind ui + ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); + nameInputLayout.setHint(getString(R.string.name)); + nameInput.setText(mProvisionerName); + nameInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + if (TextUtils.isEmpty(s.toString())) { + nameInputLayout.setError(getString(R.string.error_empty_name)); + } else { + nameInputLayout.setError(null); + } + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }); + + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()) + .setView(rootView) + .setIcon(R.drawable.ic_lan_black_alpha_24dp) + .setTitle(R.string.title_provisioner_name) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null); + + summary.setText(R.string.provisioner_name_rationale); + + final AlertDialog alertDialog = alertDialogBuilder.show(); + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { + final String name = nameInput.getEditableText().toString().trim(); + try { + if (((DialogFragmentProvisionerNameListener) requireActivity()).onNameChanged(name)) { + dismiss(); + } + } catch (IllegalArgumentException ex) { + nameInputLayout.setError(ex.getMessage()); + } + }); + + return alertDialog; + } + + public interface DialogFragmentProvisionerNameListener { + + boolean onNameChanged(@NonNull final String name); + + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentSceneRange.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentSceneRange.java new file mode 100644 index 000000000..8b4c07230 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentSceneRange.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.KeyListener; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.AllocatedSceneRange; +import no.nordicsemi.android.meshprovisioner.Scene; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.RangeListener; +import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; + +public class DialogFragmentSceneRange extends DialogFragment { + + private static final String RANGE = "RANGE"; + //UI Bindings + @BindView(R.id.low_address_layout) + TextInputLayout fistSceneInputLayout; + @BindView(R.id.low_address_input) + TextInputEditText firstSceneInput; + @BindView(R.id.high_address_layout) + TextInputLayout lastSceneInputLayout; + @BindView(R.id.high_address_input) + TextInputEditText lastSceneInput; + + private AllocatedSceneRange mRange; + + public static DialogFragmentSceneRange newInstance(@Nullable final AllocatedSceneRange range) { + DialogFragmentSceneRange fragment = new DialogFragmentSceneRange(); + final Bundle args = new Bundle(); + args.putParcelable(RANGE, range); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mRange = getArguments().getParcelable(RANGE); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_range, null); + + //Bind ui + ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); + if (mRange != null) { + final String lowAddress = MeshAddress.formatAddress(mRange.getFirstScene(), false); + final String highAddress = MeshAddress.formatAddress(mRange.getLastScene(), false); + firstSceneInput.setText(lowAddress); + firstSceneInput.setSelection(lowAddress.length()); + lastSceneInput.setText(highAddress); + lastSceneInput.setSelection(highAddress.length()); + } + + final KeyListener hexKeyListener = new HexKeyListener(); + fistSceneInputLayout.setHint(getString(R.string.first_scene)); + firstSceneInput.setKeyListener(hexKeyListener); + firstSceneInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + if (TextUtils.isEmpty(s.toString())) { + fistSceneInputLayout.setError(getString(R.string.error_empty_value)); + } else { + fistSceneInputLayout.setError(null); + } + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }); + + lastSceneInputLayout.setHint(getString(R.string.last_scene)); + lastSceneInput.setKeyListener(hexKeyListener); + lastSceneInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + if (TextUtils.isEmpty(s.toString())) { + lastSceneInputLayout.setError(getString(R.string.error_empty_value)); + } else { + lastSceneInputLayout.setError(null); + } + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }); + + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()) + .setView(rootView) + .setIcon(R.drawable.ic_arrow_collapse_black_alpha_24dp) + .setTitle(R.string.title_range) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null); + + summary.setText(R.string.scene_range_summary); + + final AlertDialog alertDialog = alertDialogBuilder.show(); + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { + final String low = firstSceneInput.getEditableText().toString().trim(); + final String high = lastSceneInput.getEditableText().toString().trim(); + if (validateFirstScene(low) && validateLastScene(high)) { + try { + AllocatedSceneRange range = mRange; + if (range == null) { + range = new AllocatedSceneRange(Integer.parseInt(low, 16), Integer.parseInt(high, 16)); + } else { + range.setFirstScene(Integer.parseInt(low, 16)); + range.setLastScene(Integer.parseInt(high, 16)); + } + ((RangeListener) requireActivity()).addRange(range); + dismiss(); + } catch (IllegalArgumentException ex) { + fistSceneInputLayout.setError(ex.getMessage()); + } + } + }); + + return alertDialog; + } + + private boolean validateFirstScene(final String firstScene) { + try { + + final int address = Integer.parseInt(firstScene, 16); + if (!Scene.isValidScene(address)) { + fistSceneInputLayout.setError("first scene value must range from 0x0001 - 0xFFFF"); + return false; + } + } catch (IllegalArgumentException ex) { + fistSceneInputLayout.setError(ex.getMessage()); + return false; + } + return true; + } + + private boolean validateLastScene(final String lastScene) { + try { + + final int address = Integer.parseInt(lastScene, 16); + if (!Scene.isValidScene(address)) { + lastSceneInputLayout.setError("last scene value must range from 0x0001 - 0xFFFF"); + return false; + } + } catch (IllegalArgumentException ex) { + lastSceneInputLayout.setError(ex.getMessage()); + return false; + } + return true; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGlobalTtl.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentTtl.java similarity index 65% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGlobalTtl.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentTtl.java index e94729f39..b73085d42 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/dialog/DialogFragmentGlobalTtl.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentTtl.java @@ -20,30 +20,31 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.nrfmeshprovisioner.dialog; +package no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; import android.text.Editable; -import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import butterknife.BindView; import butterknife.ButterKnife; import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; import no.nordicsemi.android.nrfmeshprovisioner.R; -public class DialogFragmentGlobalTtl extends DialogFragment { +public class DialogFragmentTtl extends DialogFragment { private static final String GLOBAL_TTL = "GLOBAL_TTL"; @@ -53,12 +54,12 @@ public class DialogFragmentGlobalTtl extends DialogFragment { @BindView(R.id.text_input) TextInputEditText ttlInput; - private String ttl; + private int mTtl; - public static DialogFragmentGlobalTtl newInstance(final String ttl) { - DialogFragmentGlobalTtl fragmentNetworkKey = new DialogFragmentGlobalTtl(); + public static DialogFragmentTtl newInstance(final int ttl) { + DialogFragmentTtl fragmentNetworkKey = new DialogFragmentTtl(); final Bundle args = new Bundle(); - args.putString(GLOBAL_TTL, ttl); + args.putInt(GLOBAL_TTL, ttl); fragmentNetworkKey.setArguments(args); return fragmentNetworkKey; } @@ -67,19 +68,18 @@ public static DialogFragmentGlobalTtl newInstance(final String ttl) { public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { - ttl = getArguments().getString(GLOBAL_TTL); + mTtl = getArguments().getInt(GLOBAL_TTL); } } @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_ttl_input, null); + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_ttl, null); //Bind ui ButterKnife.bind(this, rootView); - ttlInput.setInputType(InputType.TYPE_CLASS_TEXT); - ttlInputLayout.setHint(getString(R.string.hint_global_ttl)); + final String ttl = String.valueOf(mTtl); ttlInput.setText(ttl); ttlInput.setSelection(ttl.length()); ttlInput.addTextChangedListener(new TextWatcher() { @@ -91,7 +91,7 @@ public void beforeTextChanged(final CharSequence s, final int start, final int c @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { if (TextUtils.isEmpty(s.toString())) { - ttlInputLayout.setError(getString(R.string.error_empty_global_ttl)); + ttlInputLayout.setError(getString(R.string.error_empty_ttl)); } else { ttlInputLayout.setError(null); } @@ -103,43 +103,44 @@ public void afterTextChanged(final Editable s) { } }); - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()).setView(rootView) + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()).setView(rootView) .setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null); alertDialogBuilder.setIcon(R.drawable.ic_timer); - alertDialogBuilder.setTitle(R.string.title_global_ttl); - alertDialogBuilder.setMessage(R.string.summary_ttl); + alertDialogBuilder.setTitle(R.string.title_ttl); final AlertDialog alertDialog = alertDialogBuilder.show(); alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - final String globalTTL = ttlInput.getText().toString(); - if (validateInput(globalTTL)) { - if(getParentFragment() == null) { - ((DialogFragmentGlobalTtlListener) getActivity()).onGlobalTtlEntered(Integer.parseInt(globalTTL)); - } else { - ((DialogFragmentGlobalTtlListener) getParentFragment()).onGlobalTtlEntered(Integer.parseInt(globalTTL)); + final String globalTTL = ttlInput.getEditableText().toString().trim(); + try { + if (validateInput(globalTTL)) { + if (((DialogFragmentTtlListener) requireActivity()).setDefaultTtl(Integer.parseInt(globalTTL))) { + dismiss(); + } } - dismiss(); + } catch (IllegalArgumentException ex) { + ttlInputLayout.setError(ex.getMessage()); } }); return alertDialog; } - private boolean validateInput(final String input) { - try { - if(MeshParserUtils.validateTtlInput(getContext(), Integer.parseInt(input))) { - return true; - } - } catch (IllegalArgumentException ex) { - ttlInputLayout.setError(ex.getMessage()); + private boolean validateInput(@NonNull final String input) { + if (TextUtils.isEmpty(input)) { + ttlInputLayout.setError("TTL value cannot be empty"); + return false; + } + + if (!MeshParserUtils.isValidDefaultTtl(Integer.parseInt(input))) { + throw new IllegalArgumentException(getString(R.string.error_invalid_default_ttl)); } - return false; + return true; } - public interface DialogFragmentGlobalTtlListener { + public interface DialogFragmentTtlListener { - void onGlobalTtlEntered(final int ttl); + boolean setDefaultTtl(final int ttl); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentUnassign.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentUnassign.java new file mode 100644 index 000000000..4551059c9 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentUnassign.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; +import no.nordicsemi.android.nrfmeshprovisioner.R; + +public class DialogFragmentUnassign extends DialogFragment { + + protected static final String TITLE = "TITLE"; + protected static final String MESSAGE = "MESSAGE"; + protected AlertDialog.Builder alertDialogBuilder; + protected String title; + protected String message; + + public interface DialogFragmentUnassignListener { + void onProvisionerUnassigned(); + } + + public static DialogFragmentUnassign newInstance(final String title, final String message) { + Bundle args = new Bundle(); + DialogFragmentUnassign fragment = new DialogFragmentUnassign(); + args.putString(TITLE, title); + args.putString(MESSAGE, message); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + super.onCreateDialog(savedInstanceState); + + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireActivity()); + alertDialogBuilder.setIcon(R.drawable.ic_reset_black_24dp_alpha); + alertDialogBuilder.setTitle(requireContext().getString(R.string.title_unassign_provisioner)); + alertDialogBuilder.setMessage(requireContext().getString(R.string.summary_unassign_provisioner)); + alertDialogBuilder.setNegativeButton(getString(R.string.no), null); + alertDialogBuilder.setPositiveButton(getString(R.string.confirm), (dialog, which) -> ( + (DialogFragmentUnassignListener) requireActivity()).onProvisionerUnassigned()); + + final AlertDialog alertDialog = alertDialogBuilder.show(); + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE) + .setTextColor(ContextCompat.getColor(requireContext(), R.color.nordicRed)); + + return alertDialog; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentUnicastRange.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentUnicastRange.java new file mode 100644 index 000000000..300c4c65b --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/provisioners/dialogs/DialogFragmentUnicastRange.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.provisioners.dialogs; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.KeyListener; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import butterknife.BindView; +import butterknife.ButterKnife; +import no.nordicsemi.android.meshprovisioner.AllocatedUnicastRange; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.provisioners.RangeListener; +import no.nordicsemi.android.nrfmeshprovisioner.utils.HexKeyListener; + +public class DialogFragmentUnicastRange extends DialogFragment { + + private static final String RANGE = "RANGE"; + //UI Bindings + @BindView(R.id.low_address_layout) + TextInputLayout lowAddressInputLayout; + @BindView(R.id.low_address_input) + TextInputEditText lowAddressInput; + @BindView(R.id.high_address_layout) + TextInputLayout highAddressInputLayout; + @BindView(R.id.high_address_input) + TextInputEditText highAddressInput; + + private AllocatedUnicastRange mRange; + + public static DialogFragmentUnicastRange newInstance(@Nullable final AllocatedUnicastRange range) { + DialogFragmentUnicastRange fragment = new DialogFragmentUnicastRange(); + final Bundle args = new Bundle(); + args.putParcelable(RANGE, range); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mRange = getArguments().getParcelable(RANGE); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + @SuppressLint("InflateParams") final View rootView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_fragment_range, null); + + //Bind ui + ButterKnife.bind(this, rootView); + final TextView summary = rootView.findViewById(R.id.summary); + if (mRange != null) { + final String lowAddress = MeshAddress.formatAddress(mRange.getLowAddress(), false); + final String highAddress = MeshAddress.formatAddress(mRange.getHighAddress(), false); + lowAddressInput.setText(lowAddress); + lowAddressInput.setSelection(lowAddress.length()); + highAddressInput.setText(highAddress); + highAddressInput.setSelection(highAddress.length()); + } + + final KeyListener hexKeyListener = new HexKeyListener(); + lowAddressInput.setKeyListener(hexKeyListener); + lowAddressInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + if (TextUtils.isEmpty(s.toString())) { + lowAddressInputLayout.setError(getString(R.string.error_empty_value)); + } else { + lowAddressInputLayout.setError(null); + } + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }); + + highAddressInput.setKeyListener(hexKeyListener); + highAddressInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + if (TextUtils.isEmpty(s.toString())) { + highAddressInputLayout.setError(getString(R.string.error_empty_value)); + } else { + highAddressInputLayout.setError(null); + } + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }); + + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireContext()) + .setView(rootView) + .setIcon(R.drawable.ic_arrow_collapse_black_alpha_24dp) + .setTitle(R.string.title_range) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null); + + summary.setText(R.string.unicast_range_summary); + + final AlertDialog alertDialog = alertDialogBuilder.show(); + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { + final String low = lowAddressInput.getEditableText().toString().trim(); + final String high = highAddressInput.getEditableText().toString().trim(); + if (validateLow(low) && validateHigh(high)) { + try { + AllocatedUnicastRange range = mRange; + if (range == null) { + range = new AllocatedUnicastRange(Integer.parseInt(low, 16), Integer.parseInt(high, 16)); + } else { + range.setLowAddress(Integer.parseInt(low, 16)); + range.setHighAddress(Integer.parseInt(high, 16)); + } + ((RangeListener) requireActivity()).addRange(range); + dismiss(); + } catch (IllegalArgumentException ex) { + lowAddressInputLayout.setError(ex.getMessage()); + } + } + }); + + return alertDialog; + } + + private boolean validateLow(final String addressValue) { + try { + + final int address = Integer.parseInt(addressValue, 16); + if (!MeshAddress.isValidUnicastAddress(address)) { + lowAddressInputLayout.setError("Unicast address value must range from 0x0001 - 0x7FFFF"); + return false; + } + } catch (IllegalArgumentException ex) { + lowAddressInputLayout.setError(ex.getMessage()); + return false; + } + return true; + } + + private boolean validateHigh(final String addressValue) { + try { + + final int address = Integer.parseInt(addressValue, 16); + if (!MeshAddress.isValidUnicastAddress(address)) { + highAddressInputLayout.setError("Unicast address value must range from 0x0001 - 0x7FFFF"); + return false; + } + } catch (IllegalArgumentException ex) { + highAddressInputLayout.setError(ex.getMessage()); + return false; + } + return true; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/AddressTypes.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/AddressTypes.java new file mode 100644 index 000000000..5586f3354 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/AddressTypes.java @@ -0,0 +1,84 @@ +package no.nordicsemi.android.nrfmeshprovisioner.utils; + +/** + * Address types + */ +@SuppressWarnings("unused") +public enum AddressTypes { + + UNICAST_ADDRESS(0), + GROUP_ADDRESS(1), + ALL_PROXIES(2), + ALL_FRIENDS(3), + ALL_RELAYS(4), + ALL_NODES(5), + VIRTUAL_ADDRESS(6); + + private int type; + + /** + * Constructs address type + * + * @param type Address type + */ + AddressTypes(final int type) { + this.type = type; + } + + /** + * Returns the address type + */ + public int getType() { + return type; + } + + /** + * Returns the oob method used for authentication + * + * @param method auth method used + */ + public static AddressTypes fromValue(final int method) { + switch (method) { + default: + return null; + case 0: + return UNICAST_ADDRESS; + case 1: + return GROUP_ADDRESS; + case 2: + return ALL_PROXIES; + case 3: + return ALL_FRIENDS; + case 4: + return ALL_RELAYS; + case 5: + return ALL_NODES; + case 6: + return VIRTUAL_ADDRESS; + } + } + + /** + * Returns the address type name + * + * @param type Address type + */ + public static String getTypeName(final AddressTypes type) { + switch (type) { + default: + return "Unicast Address"; + case GROUP_ADDRESS: + return "Group Address"; + case ALL_PROXIES: + return "All Proxies"; + case ALL_FRIENDS: + return "All Friends"; + case ALL_RELAYS: + return "All Relays"; + case ALL_NODES: + return "All Nodes"; + case VIRTUAL_ADDRESS: + return "Virtual Address"; + } + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/HexKeyListener.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/HexKeyListener.java index 04c223c21..de2b2c69d 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/HexKeyListener.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/HexKeyListener.java @@ -22,7 +22,7 @@ package no.nordicsemi.android.nrfmeshprovisioner.utils; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.InputType; import android.text.method.NumberKeyListener; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/ProvisionerStates.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/ProvisionerStates.java new file mode 100644 index 000000000..163ffde09 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/ProvisionerStates.java @@ -0,0 +1,52 @@ +package no.nordicsemi.android.nrfmeshprovisioner.utils; + +public enum ProvisionerStates { + + PROVISIONING_INVITE(0), + PROVISIONING_CAPABILITIES(1), + PROVISIONING_START(2), + PROVISIONING_PUBLIC_KEY_SENT(3), + PROVISIONING_PUBLIC_KEY_RECEIVED(4), + PROVISIONING_AUTHENTICATION_INPUT_OOB_WAITING(5), + PROVISIONING_AUTHENTICATION_OUTPUT_OOB_WAITING(6), + PROVISIONING_AUTHENTICATION_STATIC_OOB_WAITING(7), + PROVISIONING_AUTHENTICATION_INPUT_ENTERED(8), + PROVISIONING_INPUT_COMPLETE(9), + PROVISIONING_CONFIRMATION_SENT(10), + PROVISIONING_CONFIRMATION_RECEIVED(11), + PROVISIONING_RANDOM_SENT(12), + PROVISIONING_RANDOM_RECEIVED(13), + PROVISIONING_DATA_SENT(14), + PROVISIONING_COMPLETE(15), + PROVISIONING_FAILED(16), + COMPOSITION_DATA_GET_SENT(17), + COMPOSITION_DATA_STATUS_RECEIVED(18), + SENDING_DEFAULT_TTL_GET(19), + DEFAULT_TTL_STATUS_RECEIVED(20), + SENDING_APP_KEY_ADD(21), + APP_KEY_STATUS_RECEIVED(22), + SENDING_NETWORK_TRANSMIT_SET(23), + NETWORK_TRANSMIT_STATUS_RECEIVED(24), + SENDING_BLOCK_ACKNOWLEDGEMENT(98), + BLOCK_ACKNOWLEDGEMENT_RECEIVED(99), + PROVISIONER_UNASSIGNED(100); + + private int state; + + ProvisionerStates(final int state) { + this.state = state; + } + + public int getState() { + return state; + } + + public static ProvisionerStates fromStatusCode(final int statusCode) { + for (ProvisionerStates state : ProvisionerStates.values()) { + if (state.getState() == statusCode) { + return state; + } + } + throw new IllegalStateException("Invalid state"); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/Utils.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/Utils.java index 0cc0d0697..6a377d734 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/Utils.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/utils/Utils.java @@ -32,17 +32,17 @@ import android.os.ParcelUuid; import android.preference.PreferenceManager; import android.provider.Settings; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; import android.widget.Toast; import java.util.Comparator; import java.util.UUID; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; -import no.nordicsemi.android.meshprovisioner.transport.NetworkKey; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; +import no.nordicsemi.android.meshprovisioner.NetworkKey; import no.nordicsemi.android.support.v18.scanner.ScanRecord; import no.nordicsemi.android.support.v18.scanner.ScanResult; @@ -55,10 +55,13 @@ public class Utils { public static final String EXTRA_DATA_MODEL_NAME = "EXTRA_DATA_MODEL_NAME"; public static final String EXTRA_DEVICE = "EXTRA_DEVICE"; - public static final String ACTIVITY_RESULT = "RESULT_APP_KEY"; + public static final String ACTIVITY_RESULT = "RESULT_KEY"; public static final String PROVISIONING_COMPLETED = "PROVISIONING_COMPLETED"; + public static final String PROVISIONER_UNASSIGNED = "PROVISIONER_UNASSIGNED"; public static final String COMPOSITION_DATA_COMPLETED = "COMPOSITION_DATA_COMPLETED"; + public static final String DEFAULT_GET_COMPLETED = "DEFAULT_GET_COMPLETED"; public static final String APP_KEY_ADD_COMPLETED = "APP_KEY_ADD_COMPLETED"; + public static final String NETWORK_TRANSMIT_SET_COMPLETED = "NETWORK_TRANSMIT_SET_COMPLETED"; public static final String EXTRA_DATA = "EXTRA_DATA"; private static final String PREFS_LOCATION_NOT_REQUIRED = "location_not_required"; private static final String PREFS_PERMISSION_REQUESTED = "permission_requested"; @@ -66,14 +69,28 @@ public class Utils { private static final String PREFS_WRITE_STORAGE_PERMISSION_REQUESTED = "write_storage_permission_requested"; public static final int PROVISIONING_SUCCESS = 2112; public static final int CONNECT_TO_NETWORK = 2113; - public static final String RESULT_APP_KEY = "RESULT_APP_KEY"; + public static final String RESULT_KEY = "RESULT_KEY"; private static final String APPLICATION_KEYS = "APPLICATION_KEYS"; + public static final String RANGE_TYPE = "RANGE_TYPE"; + public static final String DIALOG_FRAGMENT_KEY_STATUS = "DIALOG_FRAGMENT_KEY_STATUS"; + + //Message timeout in case the message fails to lost/received + public static final int MESSAGE_TIME_OUT = 10000; + //Manage ranges + public static final int UNICAST_RANGE = 0; + public static final int GROUP_RANGE = 1; + public static final int SCENE_RANGE = 2; + + //Manage app keys + public static final int MANAGE_NET_KEY = 0; + public static final int ADD_NET_KEY = 1; //Manage app keys - public static final int MANAGE_APP_KEY = 0; - public static final int ADD_APP_KEY = 1; - public static final int BIND_APP_KEY = 2; - public static final int PUBLICATION_APP_KEY = 3; + public static final int MANAGE_APP_KEY = 2; + public static final int ADD_APP_KEY = 3; + public static final int BIND_APP_KEY = 4; + public static final int PUBLICATION_APP_KEY = 5; + public static final int SELECT_KEY = 2011; //Random number public static final Comparator netKeyComparator = (key1, key2) -> Integer.compare(key1.getKeyIndex(), key2.getKeyIndex()); @@ -173,6 +190,7 @@ public static void markLocationPermissionRequested(final Context context) { * * @return true if permissions are already granted, false otherwise. */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isWriteExternalStoragePermissionsGranted(final Context context) { return ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddAppKeyViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddAppKeyViewModel.java new file mode 100644 index 000000000..08087ba60 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddAppKeyViewModel.java @@ -0,0 +1,17 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; + +/** + * ViewModel for {@link AppKeysActivity} + */ +public class AddAppKeyViewModel extends KeysViewModel { + + @Inject + AddAppKeyViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddKeysViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddKeysViewModel.java new file mode 100644 index 000000000..41a88479b --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddKeysViewModel.java @@ -0,0 +1,59 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import java.util.LinkedList; +import java.util.Queue; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyGet; +import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.NetKeysActivity; + +/** + * ViewModel for {@link NetKeysActivity}, {@link AppKeysActivity} + */ +public class AddKeysViewModel extends KeysViewModel { + + private Queue messageQueue = new LinkedList<>(); + + @Inject + AddKeysViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } + + public Queue getMessageQueue() { + return messageQueue; + } + + /** + * Checks if the key has been added to the node + * + * @param keyIndex index of the key + * @return true if added or false otherwise + */ + public boolean isNetKeyAdded(final int keyIndex) { + final ProvisionedMeshNode node = getSelectedMeshNode().getValue(); + if (node != null) { + return MeshParserUtils.isNodeKeyExists(node.getAddedNetKeys(), keyIndex); + } + return false; + } + + /** + * Checks if the key has been added to the node + * + * @param keyIndex index of the key + * @return true if added or false otherwise + */ + public boolean isAppKeyAdded(final int keyIndex) { + final ProvisionedMeshNode node = getSelectedMeshNode().getValue(); + if (node != null) { + return MeshParserUtils.isNodeKeyExists(node.getAddedAppKeys(), keyIndex); + } + return false; + } + +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddNetKeyViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddNetKeyViewModel.java new file mode 100644 index 000000000..1f8744384 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddNetKeyViewModel.java @@ -0,0 +1,17 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; + +/** + * ViewModel for {@link AppKeysActivity} + */ +public class AddNetKeyViewModel extends KeysViewModel { + + @Inject + AddNetKeyViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddProvisionerViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddProvisionerViewModel.java new file mode 100644 index 000000000..6907e3045 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AddProvisionerViewModel.java @@ -0,0 +1,28 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import no.nordicsemi.android.meshprovisioner.Provisioner; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; + +/** + * ViewModel for {@link AppKeysActivity} + */ +public class AddProvisionerViewModel extends BaseViewModel { + + @Inject + AddProvisionerViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + mNrfMeshRepository.clearTransactionStatus(); + } + + public void setSelectedProvisioner(@NonNull final Provisioner provisioner) { + mNrfMeshRepository.setSelectedProvisioner(provisioner); + } + + public LiveData getSelectedProvisioner() { + return mNrfMeshRepository.getSelectedProvisioner(); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AppKeysViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AppKeysViewModel.java new file mode 100644 index 000000000..f67536674 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/AppKeysViewModel.java @@ -0,0 +1,17 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; + +/** + * ViewModel for {@link AppKeysActivity} + */ +public class AppKeysViewModel extends KeysViewModel { + + @Inject + AppKeysViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/BaseViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/BaseViewModel.java new file mode 100644 index 000000000..515d3c028 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/BaseViewModel.java @@ -0,0 +1,303 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import com.google.android.material.snackbar.Snackbar; + +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; +import no.nordicsemi.android.meshprovisioner.Group; +import no.nordicsemi.android.meshprovisioner.MeshManagerApi; +import no.nordicsemi.android.meshprovisioner.models.ConfigurationClientModel; +import no.nordicsemi.android.meshprovisioner.models.ConfigurationServerModel; +import no.nordicsemi.android.meshprovisioner.models.GenericLevelServerModel; +import no.nordicsemi.android.meshprovisioner.models.GenericOnOffServerModel; +import no.nordicsemi.android.meshprovisioner.models.VendorModel; +import no.nordicsemi.android.meshprovisioner.transport.Element; +import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; +import no.nordicsemi.android.meshprovisioner.transport.MeshModel; +import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; +import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; +import no.nordicsemi.android.nrfmeshprovisioner.ble.ScannerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.ConfigurationClientActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.ConfigurationServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.GenericLevelServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.GenericOnOffServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.ModelConfigurationActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.VendorModelActivity; +import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; + +/** + * abstract base class for ViewModels + */ +abstract class BaseViewModel extends ViewModel { + + final NrfMeshRepository mNrfMeshRepository; + boolean isActivityVisibile = false; + + /** + * Constructs {@link BaseViewModel} + * + * @param nRfMeshRepository Mesh Repository {@link NrfMeshRepository} + */ + BaseViewModel(@NonNull final NrfMeshRepository nRfMeshRepository) { + mNrfMeshRepository = nRfMeshRepository; + } + + @Override + protected void onCleared() { + super.onCleared(); + } + + /** + * Returns the Mesh repository + */ + public final NrfMeshRepository getNrfMeshRepository() { + return mNrfMeshRepository; + } + + /** + * Returns the {@link BleMeshManager} + */ + public final BleMeshManager getBleMeshManager() { + return mNrfMeshRepository.getBleMeshManager(); + } + + /** + * Navigate to scanner activity + * + * @param context Activity context + * @param withResult Start activity with result + * @param requestCode Request code when using with result + * @param withProvisioningService Scan with provisioning service + */ + public void navigateToScannerActivity(@NonNull final Activity context, final boolean withResult, final int requestCode, final boolean withProvisioningService) { + final Intent intent = new Intent(context, ScannerActivity.class); + intent.putExtra(Utils.EXTRA_DATA_PROVISIONING_SERVICE, withProvisioningService); + if (withResult) { + context.startActivityForResult(intent, requestCode); + } else { + context.startActivity(intent); + } + } + + /** + * Start activity based on the type of the model + * + *

This way we can seperate the ui logic for different activities

+ * + * @param context Activity context + * @param model {@link MeshModel} + */ + public void navigateToModelActivity(@NonNull final Activity context, @NonNull final MeshModel model) { + final Intent intent; + if (model instanceof ConfigurationServerModel) { + intent = new Intent(context, ConfigurationServerActivity.class); + } else if (model instanceof ConfigurationClientModel) { + intent = new Intent(context, ConfigurationClientActivity.class); + } else if (model instanceof GenericOnOffServerModel) { + intent = new Intent(context, GenericOnOffServerActivity.class); + } else if (model instanceof GenericLevelServerModel) { + intent = new Intent(context, GenericLevelServerActivity.class); + } else if (model instanceof VendorModel) { + intent = new Intent(context, VendorModelActivity.class); + } else { + intent = new Intent(context, ModelConfigurationActivity.class); + } + context.startActivity(intent); + } + + /** + * Connect to peripheral + * + * @param context Context + * @param device {@link ExtendedBluetoothDevice} device + * @param connectToNetwork True if connecting to an unprovisioned node or proxy node + */ + public final void connect(@NonNull final Context context, @NonNull final ExtendedBluetoothDevice device, final boolean connectToNetwork) { + mNrfMeshRepository.connect(context, device, connectToNetwork); + } + + /** + * Disconnect from peripheral + */ + public final void disconnect() { + mNrfMeshRepository.disconnect(); + } + + /** + * Returns the address of the connected proxy address + */ + public final LiveData getConnectedProxyAddress() { + return mNrfMeshRepository.getConnectedProxyAddress(); + } + + /** + * Returns true currently connected to a peripheral device. + */ + public final LiveData isConnected() { + return mNrfMeshRepository.isConnected(); + } + + /** + * Returns true if the device is ready + */ + public final LiveData isDeviceReady() { + return mNrfMeshRepository.isDeviceReady(); + } + + /** + * Returns the connection state + */ + public final LiveData getConnectionState() { + return mNrfMeshRepository.getConnectionState(); + } + + /** + * Returns true if currently connected to the proxy node in the mesh network. + */ + public final LiveData isConnectedToProxy() { + return mNrfMeshRepository.isConnectedToProxy(); + } + + /** + * Returns the mesh manager api + */ + public final MeshManagerApi getMeshManagerApi() { + return mNrfMeshRepository.getMeshManagerApi(); + } + + /** + * Returns live data object containing provisioning settings. + */ + public final MeshNetworkLiveData getNetworkLiveData() { + return mNrfMeshRepository.getMeshNetworkLiveData(); + } + + /** + * Returns the provisioned nodes as a live data object. + */ + public LiveData> getNodes() { + return mNrfMeshRepository.getNodes(); + } + + /** + * Get selected {@link ProvisionedMeshNode} mesh node + */ + public final LiveData getSelectedMeshNode() { + return mNrfMeshRepository.getSelectedMeshNode(); + } + + /** + * Set selected mesh node + * + * @param node {@link ProvisionedMeshNode} + */ + public final void setSelectedMeshNode(@NonNull final ProvisionedMeshNode node) { + mNrfMeshRepository.setSelectedMeshNode(node); + } + + /** + * Get selected element + */ + public final LiveData getSelectedElement() { + return mNrfMeshRepository.getSelectedElement(); + } + + /** + * Set the element to be configured + * + * @param element {@link Element} + */ + public final void setSelectedElement(@NonNull final Element element) { + mNrfMeshRepository.setSelectedElement(element); + } + + /** + * Get selected model + */ + public final LiveData getSelectedModel() { + return mNrfMeshRepository.getSelectedModel(); + } + + /** + * Set the mesh model to be configured + * + * @param model {@link MeshModel} + */ + public final void setSelectedModel(@NonNull final MeshModel model) { + mNrfMeshRepository.setSelectedModel(model); + } + + /** + * Returns the LiveData containing a list of {@link Group} + */ + public final LiveData> getGroups() { + return mNrfMeshRepository.getGroups(); + } + + /** + * Reset mesh network + */ + public final void resetMeshNetwork() { + mNrfMeshRepository.resetMeshNetwork(); + } + + /** + * Returns the LiveData containing {@link MeshMessage} + */ + public final LiveData getMeshMessage() { + return mNrfMeshRepository.getMeshMessageLiveData(); + } + + /** + * Returns an observable live data object containing the transaction status. + * + * @return {@link TransactionStatus} + */ + public final LiveData getTransactionStatus() { + return mNrfMeshRepository.getTransactionStatus(); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public boolean isModelExists(final int modelId) { + final ProvisionedMeshNode node = getSelectedMeshNode().getValue(); + return node != null && node.isExist(modelId); + } + + /** + * Display disconnected snack bar + * + * @param context Activity context + * @param container container + */ + public void displayDisconnectedSnackBar(@NonNull final Activity context, @NonNull final CoordinatorLayout container) { + Snackbar.make(container, context.getString(R.string.disconnected_network_rationale), Snackbar.LENGTH_LONG) + .setActionTextColor(context.getResources().getColor(R.color.colorPrimaryDark)) + .setAction(context.getString(R.string.action_connect), v -> + navigateToScannerActivity(context, false, Utils.CONNECT_TO_NETWORK, false)) + .show(); + } + + /** + * Display snack bar + * + * @param context Activity context + * @param container Coordinator layout + * @param message Message + * @param duration Snack bar duration + */ + public void displaySnackBar(@NonNull final Activity context, @NonNull final CoordinatorLayout container, @NonNull final String message, final int duration) { + Snackbar.make(container, message, duration) + .setActionTextColor(context.getResources().getColor(R.color.colorPrimaryDark)) + .show(); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/EditAppKeyViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/EditAppKeyViewModel.java new file mode 100644 index 000000000..e089bd6cb --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/EditAppKeyViewModel.java @@ -0,0 +1,17 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; + +/** + * ViewModel for {@link AppKeysActivity} + */ +public class EditAppKeyViewModel extends KeysViewModel { + + @Inject + EditAppKeyViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/EditNetKeyViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/EditNetKeyViewModel.java new file mode 100644 index 000000000..483b271a5 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/EditNetKeyViewModel.java @@ -0,0 +1,17 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; + +/** + * ViewModel for {@link AppKeysActivity} + */ +public class EditNetKeyViewModel extends KeysViewModel { + + @Inject + EditNetKeyViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/EditProvisionerViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/EditProvisionerViewModel.java new file mode 100644 index 000000000..537a2e367 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/EditProvisionerViewModel.java @@ -0,0 +1,27 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import no.nordicsemi.android.meshprovisioner.Provisioner; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; + +/** + * ViewModel for {@link AppKeysActivity} + */ +public class EditProvisionerViewModel extends BaseViewModel { + + @Inject + EditProvisionerViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } + + public void setSelectedProvisioner(@NonNull final Provisioner provisioner) { + mNrfMeshRepository.setSelectedProvisioner(provisioner); + } + + public LiveData getSelectedProvisioner() { + return mNrfMeshRepository.getSelectedProvisioner(); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ExtendedMeshNode.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ExtendedMeshNode.java index e9a5288f7..fff6c55d5 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ExtendedMeshNode.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ExtendedMeshNode.java @@ -22,7 +22,7 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; +import androidx.lifecycle.LiveData; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/GroupControlsViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/GroupControlsViewModel.java index 34ae9f21a..caf20b159 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/GroupControlsViewModel.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/GroupControlsViewModel.java @@ -22,31 +22,23 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; -import android.content.Context; - -import java.util.List; - import javax.inject.Inject; +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; import no.nordicsemi.android.meshprovisioner.Group; -import no.nordicsemi.android.meshprovisioner.MeshManagerApi; -import no.nordicsemi.android.meshprovisioner.transport.Element; -import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; -import no.nordicsemi.android.meshprovisioner.transport.MeshModel; -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; -import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; +import no.nordicsemi.android.nrfmeshprovisioner.GroupControlsActivity; -public class GroupControlsViewModel extends ViewModel { +/** + * ViewModel for {@link GroupControlsActivity} + */ +public class GroupControlsViewModel extends BaseViewModel { - private final NrfMeshRepository mNrfMeshRepository; private final ScannerRepository mScannerRepository; @Inject - GroupControlsViewModel(final NrfMeshRepository nrfMeshRepository, final ScannerRepository scannerRepository) { - this.mNrfMeshRepository = nrfMeshRepository; + GroupControlsViewModel(@NonNull final NrfMeshRepository nrfMeshRepository, @NonNull final ScannerRepository scannerRepository) { + super(nrfMeshRepository); this.mScannerRepository = scannerRepository; scannerRepository.registerBroadcastReceivers(); } @@ -57,111 +49,10 @@ protected void onCleared() { mScannerRepository.unregisterBroadcastReceivers(); } - public LiveData isDeviceReady() { - return mNrfMeshRepository.isDeviceReady(); - } - - public LiveData getConnectionState() { - return mNrfMeshRepository.getConnectionState(); - } - - public LiveData isConnected() { - return mNrfMeshRepository.isConnected(); - } - - public void connect(final Context context, final ExtendedBluetoothDevice device, final boolean connectToNetwork) { - mNrfMeshRepository.connect(context, device, connectToNetwork); - } - - public void disconnect() { - mNrfMeshRepository.disconnect(); - } - - public NrfMeshRepository getNrfMeshRepository() { - return mNrfMeshRepository; - } - - public BleMeshManager getBleMeshManager() { - return mNrfMeshRepository.getBleMeshManager(); - } - - public MeshManagerApi getMeshManagerApi() { - return mNrfMeshRepository.getMeshManagerApi(); - } - /** - * Returns an instance of the scanner repository + * Returns the selected LiveData {@link Group} */ - public ScannerRepository getScannerRepository() { - return mScannerRepository; - } - public LiveData getSelectedGroup() { return mNrfMeshRepository.getSelectedGroup(); } - /** - * Returns the provisioned nodes as a live data object. - */ - public LiveData> getProvisionedNodes() { - return mNrfMeshRepository.getProvisionedNodes(); - } - - /** - * Returns if currently connected to the mesh network. - * - * @return true if connected and false otherwise - */ - public LiveData isConnectedToProxy() { - return mNrfMeshRepository.isConnectedToProxy(); - } - - public MeshNetworkLiveData getMeshNetworkLiveData() { - return mNrfMeshRepository.getMeshNetworkLiveData(); - } - - public LiveData getMeshMessageLiveData() { - return mNrfMeshRepository.getMeshMessageLiveData(); - } - - /** - * Set the mesh node to be configured - * - * @param meshNode provisioned mesh node - */ - public void setSelectedMeshNode(final ProvisionedMeshNode meshNode) { - mNrfMeshRepository.setSelectedMeshNode(meshNode); - } - - /** - * Set the element to be configured - * - * @param element {@link Element} - */ - public void setSelectedElement(final Element element) { - mNrfMeshRepository.setSelectedElement(element); - } - - /** - * Get selected model - */ - public LiveData getSelectedModel() { - return mNrfMeshRepository.getSelectedModel(); - } - - /** - * Set the mesh model to be configured - * - * @param model {@link MeshModel} - */ - public void setSelectedModel(final MeshModel model) { - mNrfMeshRepository.setSelectedModel(model); - } - /** - * Get selected mesh node - * - * @return {@link ExtendedMeshNode} element - */ - public LiveData getSelectedMeshNode() { - return mNrfMeshRepository.getSelectedMeshNode(); - } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/KeysViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/KeysViewModel.java new file mode 100644 index 000000000..d318d227f --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/KeysViewModel.java @@ -0,0 +1,15 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; +import no.nordicsemi.android.nrfmeshprovisioner.keys.NetKeysActivity; + +/** + * ViewModel for {@link NetKeysActivity}, {@link AppKeysActivity} + */ +abstract class KeysViewModel extends BaseViewModel { + + KeysViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ManageAppKeysViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ManageAppKeysViewModel.java deleted file mode 100644 index 08c6cc78e..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ManageAppKeysViewModel.java +++ /dev/null @@ -1,33 +0,0 @@ -package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; - -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; - -import javax.inject.Inject; - -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.nrfmeshprovisioner.ManageAppKeysActivity; - -/** - * ViewModel for {@link ManageAppKeysActivity} - */ -public class ManageAppKeysViewModel extends ViewModel { - - private final NrfMeshRepository mNrfMeshRepository; - - @Inject - ManageAppKeysViewModel(final NrfMeshRepository nrfMeshRepository) { - mNrfMeshRepository = nrfMeshRepository; - } - - /** - * Returns live data object containing provisioning settings. - */ - public MeshNetworkLiveData getMeshNetworkLiveData() { - return mNrfMeshRepository.getMeshNetworkLiveData(); - } - - public LiveData getSelectedMeshNode() { - return mNrfMeshRepository.getSelectedMeshNode(); - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshMessageLiveData.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshMessageLiveData.java index 55aa9aaed..56293f2d1 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshMessageLiveData.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshMessageLiveData.java @@ -1,7 +1,9 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LifecycleOwner; -import android.arch.lifecycle.Observer; +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.Observer; import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; @@ -13,7 +15,8 @@ public void postValue(final MeshMessage value) { } @Override - public void observe(final LifecycleOwner owner, final Observer observer) { + @MainThread + public void observe(@NonNull final LifecycleOwner owner, @NonNull final Observer observer) { super.observe(owner, observer); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshNetworkLiveData.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshNetworkLiveData.java index 66aafe6c4..d4b9687b7 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshNetworkLiveData.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshNetworkLiveData.java @@ -22,15 +22,16 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.support.annotation.NonNull; +import android.text.TextUtils; import java.util.List; +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; import no.nordicsemi.android.meshprovisioner.MeshNetwork; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; -import no.nordicsemi.android.meshprovisioner.transport.NetworkKey; -import no.nordicsemi.android.meshprovisioner.utils.AddressUtils; +import no.nordicsemi.android.meshprovisioner.NetworkKey; +import no.nordicsemi.android.meshprovisioner.Provisioner; /** * LiveData class for storing {@link MeshNetwork} @@ -69,21 +70,8 @@ void refresh(@NonNull final MeshNetwork meshNetwork) { postValue(this); } - /** - * Returns the primary network key in the mesh network - */ - public NetworkKey getPrimaryNetworkKey() { - return meshNetwork.getPrimaryNetworkKey(); - } - - /** - * Sets primary network key - * - * @param networkKey network key - */ - public void setPrimaryNetworkKey(final String networkKey) { - meshNetwork.addNetKey(0, networkKey); - postValue(this); + public List getNetworkKeys() { + return meshNetwork.getNetKeys(); } /** @@ -94,104 +82,14 @@ public List getAppKeys() { } /** - * Returns the network key index - */ - public int getKeyIndex() { - return meshNetwork.getNetKeys().get(0).getKeyIndex(); - } - - /** - * Set network key index - * - * @param keyIndex network key index - */ - public void setKeyIndex(final int keyIndex) { - meshNetwork.getNetKeys().get(0).setKeyIndex(keyIndex); - postValue(this); - } - - /** - * Returns the IV Index used for provisioning - * - * @return iv index - */ - public int getIvIndex() { - return meshNetwork.getIvIndex(); - } - - /** - * Set IV Index - * - * @param ivIndex 24-bit iv index - */ - public void setIvIndex(final int ivIndex) { - meshNetwork.setIvIndex(ivIndex); - postValue(this); - } - - /** - * Returns unicast address - * - * @return 16-bit unicast address - */ - public int getUnicastAddress() { - final byte[] unicast = AddressUtils.getUnicastAddressBytes(meshNetwork.getUnicastAddress()); - return AddressUtils.getUnicastAddressInt(unicast); - } - - /** - * Set unicast address, this would be the address assigned to an unprovisioned node. - * - * @param unicastAddress 16-bit unicast address - */ - public void setUnicastAddress(final int unicastAddress) { - meshNetwork.assignUnicastAddress(unicastAddress); - postValue(this); - } - - public byte[] getProvisionerAddress() { - return AddressUtils.getUnicastAddressBytes(meshNetwork.getProvisionerAddress()); - } - - public boolean setProvisionerAddress(final int address) { - final boolean flag = meshNetwork.setProvisionerAddress(address); - if (flag) { - postValue(this); - } - return flag; - } - - /** - * Provisioning flags - */ - public int getFlags() { - return meshNetwork.getProvisioningFlags(); - } - - /** - * Provisioning flags - * - * @param flags provisioning flags - */ - public void setFlags(final int flags) { - postValue(this); - } - - /** - * Returns the global ttl set for the messages sent by the provisioner + * Returns the list of {@link Provisioner} */ - public int getGlobalTtl() { - return meshNetwork.getGlobalTtl(); + public List getProvisioners() { + return meshNetwork.getProvisioners(); } - /** - * Sets a global ttl value that would be used on all messages sent from the provisioner - * - * @param globalTtl ttl value - */ - public void setGlobalTtl(final int globalTtl) { - meshNetwork.setGlobalTtl(globalTtl); - postValue(this); + public Provisioner getProvisioner() { + return meshNetwork.getSelectedProvisioner(); } /** @@ -213,54 +111,10 @@ public void setSelectedAppKey(final ApplicationKey appKey) { postValue(this); } - public void resetSelectedAppKey(){ + public void resetSelectedAppKey() { this.selectedAppKey = null; } - /** - * Adds an application key to the next available index in the global app key list - * @param applicationKey key {@link ApplicationKey} - */ - public void addAppKey(final String applicationKey) { - if (meshNetwork != null) { - meshNetwork.addAppKey(applicationKey); - } - postValue(this); - } - - /** - * Adds an application key to the mesh network - */ - public void addAppKey(final ApplicationKey applicationKey) { - if (meshNetwork != null) { - meshNetwork.addAppKey(applicationKey); - } - postValue(this); - } - - /** - * Update the application key in a particular position - * @param keyIndex update app key in given key index - * @param applicationKey app key - */ - public void updateAppKey(final int keyIndex, final String applicationKey) { - if (meshNetwork != null) { - meshNetwork.updateAppKey(keyIndex, applicationKey); - } - postValue(this); - } - - /** - * Remove app key from the list of application keys in the mesh network - * @param appKey key {@link ApplicationKey} - */ - public void removeAppKey(final ApplicationKey appKey) { - if (meshNetwork != null) { - meshNetwork.removeAppKey(appKey); - } - postValue(this); - } - /** * Returns the network name */ @@ -270,19 +124,21 @@ public String getNetworkName() { /** * Set the network name of the mesh network + * * @param name network name */ public void setNetworkName(final String name) { meshNetwork.setMeshName(name); postValue(this); } + /** * Sets the node name * * @param nodeName node name */ - public void setNodeName(final String nodeName) { - if (nodeName != null && !nodeName.isEmpty()) { + public void setNodeName(@NonNull final String nodeName) { + if (!TextUtils.isEmpty(nodeName)) { this.nodeName = nodeName; postValue(this); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ModelConfigurationViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ModelConfigurationViewModel.java index 1fe39fcd3..e3f65ada2 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ModelConfigurationViewModel.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ModelConfigurationViewModel.java @@ -22,91 +22,55 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; - -import java.util.List; +import java.util.LinkedList; +import java.util.Queue; import javax.inject.Inject; -import no.nordicsemi.android.meshprovisioner.Group; -import no.nordicsemi.android.meshprovisioner.MeshManagerApi; -import no.nordicsemi.android.meshprovisioner.transport.Element; +import androidx.annotation.NonNull; import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; -import no.nordicsemi.android.meshprovisioner.transport.MeshModel; -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.nrfmeshprovisioner.ConfigurationServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.ConfigurationClientActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.ConfigurationServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.GenericLevelServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.GenericOnOffServerActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.ModelConfigurationActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.VendorModelActivity; /** - * View Model class for {@link ConfigurationServerActivity} + * Generic View Model class for {@link ConfigurationServerActivity},{@link ConfigurationClientActivity}, + * {@link GenericOnOffServerActivity}, {@link GenericLevelServerActivity}, {@link VendorModelActivity}, + * {@link ModelConfigurationActivity} */ -public class ModelConfigurationViewModel extends ViewModel { +public class ModelConfigurationViewModel extends BaseViewModel { - private final NrfMeshRepository mNrfMeshRepository; + private Queue messageQueue = new LinkedList<>(); @Inject - ModelConfigurationViewModel(final NrfMeshRepository nrfMeshRepository) { - this.mNrfMeshRepository = nrfMeshRepository; - } - - public LiveData isConnectedToProxy() { - return mNrfMeshRepository.isConnectedToProxy(); - } - - /** - * Returns the Mesh repository - */ - public NrfMeshRepository getNrfMeshRepository() { - return mNrfMeshRepository; - } - - /** - * Returns the mesh manager api - */ - public MeshManagerApi getMeshManagerApi() { - return mNrfMeshRepository.getMeshManagerApi(); - } - - /** - * Returns an observable live data object containing the mesh message received - * - * @return {@link MeshMessageLiveData} - */ - public LiveData getMeshMessageLiveData() { - return mNrfMeshRepository.getMeshMessageLiveData(); + ModelConfigurationViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); } - /** - * Get selected mesh node - */ - public LiveData getSelectedMeshNode() { - return mNrfMeshRepository.getSelectedMeshNode(); + @Override + protected void onCleared() { + super.onCleared(); + mNrfMeshRepository.clearTransactionStatus(); + messageQueue.clear(); } - /** - * Get selected element - */ - public LiveData getSelectedElement() { - return mNrfMeshRepository.getSelectedElement(); + public Queue getMessageQueue() { + return messageQueue; } - /** - * Get selected model - */ - public LiveData getSelectedModel() { - return mNrfMeshRepository.getSelectedModel(); + public void removeMessage() { + if (!messageQueue.isEmpty()) + messageQueue.remove(); } - /** - * Returns an observable live data object containing the transaction status. - * - * @return {@link TransactionStatusLiveData} - */ - public TransactionStatusLiveData getTransactionStatus() { - return mNrfMeshRepository.getTransactionStatusLiveData(); + public boolean isActivityVisibile() { + return isActivityVisibile; } - public LiveData> getGroups(){ - return mNrfMeshRepository.getGroups(); + public void setActivityVisible(final boolean visible){ + isActivityVisibile = visible; } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NetKeysViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NetKeysViewModel.java new file mode 100644 index 000000000..e5c1c77a3 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NetKeysViewModel.java @@ -0,0 +1,17 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; + +/** + * ViewModel for {@link AppKeysActivity} + */ +public class NetKeysViewModel extends KeysViewModel { + + @Inject + NetKeysViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NetworkInformation.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NetworkInformation.java deleted file mode 100644 index cb236d5d0..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NetworkInformation.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2018, Nordic Semiconductor - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; - -import android.content.Context; -import android.content.SharedPreferences; - -/** - * Contains the default information network and node name. Changing the network name will save the data in to the shared preferences - */ -@SuppressWarnings("unused") -public class NetworkInformation { - - private String networkName = "nRF Mesh Network"; - private String nodeName = "nRF Mesh Node"; - private String NETWORK_NAME_PREFS = "NETWORK_NAME_PREFS"; - private String NETWORK_NAME = "NETWORK_NAME"; - - private NetworkInformationListener mListener; - - /** - * Interface that listens to changes made on network information. - */ - interface NetworkInformationListener { - void onNetworkInformationUpdated(final NetworkInformation networkInformation); - } - - public NetworkInformation(final Context context) { - loadNetworkName(context); - } - - /** - * Set the network information listener - */ - void setNetworkInformationListener(final NetworkInformationListener listener) { - mListener = listener; - } - - /** - * Remove the network information listener - */ - void removeNetworkInformationListener() { - mListener = null; - } - - public void refreshProvisioningData() { - networkName = "nRF Mesh Network"; - nodeName = "nRF Mesh Node"; - if (mListener != null) { - mListener.onNetworkInformationUpdated(this); - } - } - - /** - * Sets the node name - * - * @param nodeName node name - */ - public void setNodeName(final String nodeName) { - if (nodeName != null && !nodeName.isEmpty()) { - this.nodeName = nodeName; - if (mListener != null) { - mListener.onNetworkInformationUpdated(this); - } - } - } - - /** - * Returns the node name - */ - public String getNodeName() { - return nodeName; - } - - /** - * Returns the network name - */ - public String getNetworkName() { - return networkName; - } - - /** - * Sets the network name. This will be saved locally to shared preferences. - * - * @param context application context - * @param networkName network name - */ - public void setNetworkName(final Context context, final String networkName) { - this.networkName = networkName; - saveNetworkName(context); - if (mListener != null) { - mListener.onNetworkInformationUpdated(this); - } - } - - /** - * Loads the saved network name from the preferences. - * - * @param context application context - */ - private void loadNetworkName(final Context context) { - final SharedPreferences preferences = context.getSharedPreferences(NETWORK_NAME_PREFS, Context.MODE_PRIVATE); - networkName = preferences.getString(NETWORK_NAME, networkName); - } - - /** - * Saves the network name to shared preferences. - * - * @param context Application context - */ - private void saveNetworkName(final Context context) { - final SharedPreferences preferences = context.getSharedPreferences(NETWORK_NAME_PREFS, Context.MODE_PRIVATE); - final SharedPreferences.Editor editor = preferences.edit(); - editor.putString(NETWORK_NAME, networkName); - editor.apply(); - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NetworkInformationLiveData.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NetworkInformationLiveData.java deleted file mode 100644 index e0fc95fc5..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NetworkInformationLiveData.java +++ /dev/null @@ -1,34 +0,0 @@ -package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; - -import android.arch.lifecycle.LiveData; - -/** - * This class is used to observes the updates happening in the {@link NetworkInformation} - */ -public class NetworkInformationLiveData extends LiveData { - - private NetworkInformation mNetworkInformation; - - private final NetworkInformation.NetworkInformationListener mListener = new NetworkInformation.NetworkInformationListener() { - @Override - public void onNetworkInformationUpdated(final NetworkInformation networkInformation) { - mNetworkInformation = networkInformation; - postValue(networkInformation); - } - }; - - NetworkInformationLiveData (final NetworkInformation networkInformation){ - this.mNetworkInformation = networkInformation; - postValue(networkInformation); - } - - @Override - protected void onActive() { - mNetworkInformation.setNetworkInformationListener(mListener); - } - - @Override - protected void onInactive() { - mNetworkInformation.removeNetworkInformationListener(); - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NodeConfigurationViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NodeConfigurationViewModel.java index d6b34d5a3..089817b59 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NodeConfigurationViewModel.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NodeConfigurationViewModel.java @@ -22,86 +22,35 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; - import javax.inject.Inject; -import no.nordicsemi.android.meshprovisioner.MeshManagerApi; -import no.nordicsemi.android.meshprovisioner.transport.Element; -import no.nordicsemi.android.meshprovisioner.transport.MeshMessage; -import no.nordicsemi.android.meshprovisioner.transport.MeshModel; +import androidx.annotation.NonNull; + +import no.nordicsemi.android.meshprovisioner.models.SigModelParser; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.nrfmeshprovisioner.NodeConfigurationActivity; +import no.nordicsemi.android.nrfmeshprovisioner.node.NodeConfigurationActivity; /** * View model class for {@link NodeConfigurationActivity} */ -public class NodeConfigurationViewModel extends ViewModel { - - private final NrfMeshRepository mNrfMeshRepository; +public class NodeConfigurationViewModel extends BaseViewModel { @Inject - NodeConfigurationViewModel(final NrfMeshRepository nrfMeshRepository) { - this.mNrfMeshRepository = nrfMeshRepository; - } - - public LiveData getSelectedMeshNode() { - return mNrfMeshRepository.getSelectedMeshNode(); - } - - /** - * Set the element to be configured - * - * @param element {@link Element} - */ - public void setSelectedElement(final Element element) { - mNrfMeshRepository.setSelectedElement(element); - } - - /** - * Set the mesh model to be configured - * - * @param model {@link MeshModel} - */ - public void setSelectedModel(final MeshModel model) { - mNrfMeshRepository.setSelectedModel(model); + NodeConfigurationViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); } - public LiveData isConnected() { - return mNrfMeshRepository.isConnected(); + @Override + protected void onCleared() { + super.onCleared(); + mNrfMeshRepository.clearTransactionStatus(); } - public LiveData isConnectedToProxy() { - return mNrfMeshRepository.isConnected(); + public boolean isActivityVisibile() { + return isActivityVisibile; } - public LiveData getMeshMessageLiveData() { - return mNrfMeshRepository.getMeshMessageLiveData(); + public void setActivityVisible(final boolean visible){ + isActivityVisibile = visible; } - - /** - * Returns an observable live data object containing the transaction status. - * - * @return {@link TransactionStatusLiveData} - */ - public TransactionStatusLiveData getTransactionStatus() { - return mNrfMeshRepository.getTransactionStatusLiveData(); - } - - /** - * Returns the {@link MeshManagerApi} - */ - public MeshManagerApi getMeshManagerApi() { - return mNrfMeshRepository.getMeshManagerApi(); - } - - public NrfMeshRepository getNrfMeshRepository(){ - return mNrfMeshRepository; - } - - public LiveData getConnectedMeshNodeAddress(){ - return mNrfMeshRepository.getConnectedMeshNodeAddress(); - } - } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NodeDetailsViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NodeDetailsViewModel.java new file mode 100644 index 000000000..3d0957c1b --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NodeDetailsViewModel.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.node.NodeConfigurationActivity; + +/** + * View model class for {@link NodeConfigurationActivity} + */ +public class NodeDetailsViewModel extends BaseViewModel { + + @Inject + NodeDetailsViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NrfMeshRepository.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NrfMeshRepository.java index 981baa93f..85b3f834c 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NrfMeshRepository.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/NrfMeshRepository.java @@ -1,47 +1,51 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.MutableLiveData; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.net.Uri; import android.os.Environment; import android.os.Handler; import android.os.ParcelUuid; -import android.support.annotation.NonNull; import android.util.Log; import java.io.File; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.UUID; +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import no.nordicsemi.android.log.LogSession; import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.meshprovisioner.ApplicationKey; import no.nordicsemi.android.meshprovisioner.Group; import no.nordicsemi.android.meshprovisioner.MeshManagerApi; import no.nordicsemi.android.meshprovisioner.MeshManagerCallbacks; import no.nordicsemi.android.meshprovisioner.MeshNetwork; import no.nordicsemi.android.meshprovisioner.MeshProvisioningStatusCallbacks; import no.nordicsemi.android.meshprovisioner.MeshStatusCallbacks; +import no.nordicsemi.android.meshprovisioner.NetworkKey; import no.nordicsemi.android.meshprovisioner.Provisioner; import no.nordicsemi.android.meshprovisioner.UnprovisionedBeacon; import no.nordicsemi.android.meshprovisioner.models.SigModelParser; import no.nordicsemi.android.meshprovisioner.provisionerstates.ProvisioningState; import no.nordicsemi.android.meshprovisioner.provisionerstates.UnprovisionedMeshNode; -import no.nordicsemi.android.meshprovisioner.transport.ApplicationKey; import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyAdd; import no.nordicsemi.android.meshprovisioner.transport.ConfigAppKeyStatus; import no.nordicsemi.android.meshprovisioner.transport.ConfigCompositionDataGet; import no.nordicsemi.android.meshprovisioner.transport.ConfigCompositionDataStatus; +import no.nordicsemi.android.meshprovisioner.transport.ConfigDefaultTtlGet; +import no.nordicsemi.android.meshprovisioner.transport.ConfigDefaultTtlStatus; import no.nordicsemi.android.meshprovisioner.transport.ConfigModelAppStatus; import no.nordicsemi.android.meshprovisioner.transport.ConfigModelPublicationStatus; import no.nordicsemi.android.meshprovisioner.transport.ConfigModelSubscriptionStatus; +import no.nordicsemi.android.meshprovisioner.transport.ConfigNetworkTransmitSet; import no.nordicsemi.android.meshprovisioner.transport.ConfigNetworkTransmitStatus; import no.nordicsemi.android.meshprovisioner.transport.ConfigNodeResetStatus; import no.nordicsemi.android.meshprovisioner.transport.ConfigProxyStatus; import no.nordicsemi.android.meshprovisioner.transport.ConfigRelayStatus; +import no.nordicsemi.android.meshprovisioner.transport.ControlMessage; import no.nordicsemi.android.meshprovisioner.transport.Element; import no.nordicsemi.android.meshprovisioner.transport.GenericLevelStatus; import no.nordicsemi.android.meshprovisioner.transport.GenericOnOffStatus; @@ -50,11 +54,11 @@ import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; import no.nordicsemi.android.meshprovisioner.transport.ProxyConfigFilterStatus; import no.nordicsemi.android.meshprovisioner.transport.VendorModelMessageStatus; -import no.nordicsemi.android.meshprovisioner.utils.AddressUtils; import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManagerCallbacks; +import no.nordicsemi.android.nrfmeshprovisioner.utils.ProvisionerStates; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat; import no.nordicsemi.android.support.v18.scanner.ScanCallback; @@ -74,89 +78,61 @@ public class NrfMeshRepository implements MeshProvisioningStatusCallbacks, MeshS "Nordic Semiconductor" + File.separator + "nRF Mesh" + File.separator; private static final String EXPORTED_PATH = "sdcard" + File.separator + "Nordic Semiconductor" + File.separator + "nRF Mesh" + File.separator; - /** - * Connection States Connecting, Connected, Disconnecting, Disconnected etc. - **/ + // Connection States Connecting, Connected, Disconnecting, Disconnected etc. private final MutableLiveData mIsConnectedToProxy = new MutableLiveData<>(); - /** - * Live data flag containing connected state. - **/ + // Live data flag containing connected state. private MutableLiveData mIsConnected; - /** - * LiveData to notify when device is ready - **/ + // LiveData to notify when device is ready private final MutableLiveData mOnDeviceReady = new MutableLiveData<>(); - /** - * Updates the connection state while connecting to a peripheral - **/ + // Updates the connection state while connecting to a peripheral private final MutableLiveData mConnectionState = new MutableLiveData<>(); - /** - * Flag to determine if a reconnection is in the progress when provisioning has completed - **/ + // Flag to determine if a reconnection is in the progress when provisioning has completed private final SingleLiveEvent mIsReconnecting = new SingleLiveEvent<>(); - private final MutableLiveData mUnprovisionedMeshNodeLiveData = new MutableLiveData<>(); - private final MutableLiveData mProvisionedMeshNodeLiveData = new MutableLiveData<>(); + private final SingleLiveEvent mConnectedProxyAddress = new SingleLiveEvent<>(); - private final SingleLiveEvent mConnectedMeshNodeAddress = new SingleLiveEvent<>(); - /** - * Contains the initial provisioning live data - **/ - private NetworkInformationLiveData mNetworkInformationLiveData; + private boolean mIsProvisioningComplete = false; // Flag to determine if provisioning was completed - /** - * Flag to determine if provisioning was completed - **/ - private boolean mIsProvisioningComplete = false; - - /** - * Holds the selected MeshNode to configure - **/ + // Holds the selected MeshNode to configure private MutableLiveData mExtendedMeshNode = new MutableLiveData<>(); - /** - * Holds the selected Element to configure - **/ + // Holds the selected Element to configure private MutableLiveData mSelectedElement = new MutableLiveData<>(); - /** - * Holds the selected mesh model to configure - **/ + // Holds the selected mesh model to configure private MutableLiveData mSelectedModel = new MutableLiveData<>(); + // Holds the selected app key to configure + private MutableLiveData mSelectedNetKey = new MutableLiveData<>(); + // Holds the selected app key to configure + private MutableLiveData mSelectedAppKey = new MutableLiveData<>(); + // Holds the selected provisioner when adding/editing + private MutableLiveData mSelectedProvisioner = new MutableLiveData<>(); private final MutableLiveData mSelectedGroupLiveData = new MutableLiveData<>(); - /** - * Composition data status - **/ + + // Composition data status final SingleLiveEvent mCompositionDataStatus = new SingleLiveEvent<>(); - /** - * App key add status - **/ + // App key add status final SingleLiveEvent mAppKeyStatus = new SingleLiveEvent<>(); - /** - * Contains the MeshNetwork - **/ + //Contains the MeshNetwork private MeshNetworkLiveData mMeshNetworkLiveData = new MeshNetworkLiveData(); - private SingleLiveEvent mNetworkImportState = new SingleLiveEvent<>(); private SingleLiveEvent mNetworkExportState = new SingleLiveEvent<>(); - private SingleLiveEvent mMeshMessageLiveData = new SingleLiveEvent<>(); - /** - * Contains the provisioned nodes - **/ + + // Contains the provisioned nodes private final MutableLiveData> mProvisionedNodes = new MutableLiveData<>(); private final MutableLiveData> mGroups = new MutableLiveData<>(); - private final TransactionStatusLiveData mTransactionFailedLiveData = new TransactionStatusLiveData(); + private final MutableLiveData mTransactionStatus = new SingleLiveEvent<>(); private MeshManagerApi mMeshManagerApi; private BleMeshManager mBleMeshManager; @@ -169,7 +145,9 @@ public class NrfMeshRepository implements MeshProvisioningStatusCallbacks, MeshS private ProvisioningStatusLiveData mProvisioningStateLiveData; private MeshNetwork mMeshNetwork; private boolean mIsCompositionDataReceived; + private boolean mIsDefaultTtlReceived; private boolean mIsAppKeyAddCompleted; + private boolean mIsNetworkRetransmitSetCompleted; private final Runnable mReconnectRunnable = this::startScan; @@ -179,7 +157,6 @@ public class NrfMeshRepository implements MeshProvisioningStatusCallbacks, MeshS }; public NrfMeshRepository(final MeshManagerApi meshManagerApi, - final NetworkInformation networkInformation, final BleMeshManager bleMeshManager) { //Initialize the mesh api mMeshManagerApi = meshManagerApi; @@ -237,15 +214,23 @@ boolean isCompositionDataStatusReceived() { return mIsCompositionDataReceived; } + boolean isDefaultTtlReceived() { + return mIsDefaultTtlReceived; + } + boolean isAppKeyAddCompleted() { return mIsAppKeyAddCompleted; } + boolean isNetworkRetransmitSetCompleted() { + return mIsNetworkRetransmitSetCompleted; + } + final MeshNetworkLiveData getMeshNetworkLiveData() { return mMeshNetworkLiveData; } - LiveData> getProvisionedNodes() { + LiveData> getNodes() { return mProvisionedNodes; } @@ -261,16 +246,21 @@ LiveData getNetworkExportState() { return mNetworkExportState; } - NetworkInformationLiveData getNetworkInformationLiveData() { - return mNetworkInformationLiveData; - } - ProvisioningStatusLiveData getProvisioningState() { return mProvisioningStateLiveData; } - TransactionStatusLiveData getTransactionStatusLiveData() { - return mTransactionFailedLiveData; + LiveData getTransactionStatus() { + return mTransactionStatus; + } + + /** + * Clears the transaction status + */ + void clearTransactionStatus() { + if (mTransactionStatus.getValue() != null) { + mTransactionStatus.postValue(null); + } } /** @@ -294,7 +284,7 @@ BleMeshManager getBleMeshManager() { /** * Returns the {@link MeshMessageLiveData} live data object containing the mesh message */ - public LiveData getMeshMessageLiveData() { + LiveData getMeshMessageLiveData() { return mMeshMessageLiveData; } @@ -313,14 +303,18 @@ void resetMeshNetwork() { /** * Connect to peripheral * - * @param device bluetooth device + * @param context Context + * @param device {@link ExtendedBluetoothDevice} device + * @param connectToNetwork True if connecting to an unprovisioned node or proxy node */ void connect(final Context context, final ExtendedBluetoothDevice device, final boolean connectToNetwork) { - mMeshNetworkLiveData.getValue().setNodeName(device.getName()); + mMeshNetworkLiveData.setNodeName(device.getName()); mIsProvisioningComplete = false; mIsCompositionDataReceived = false; + mIsDefaultTtlReceived = false; mIsAppKeyAddCompleted = false; - clearExtendedMeshNode(); + mIsNetworkRetransmitSetCompleted = false; + //clearExtendedMeshNode(); final LogSession logSession = Logger.newSession(context, null, device.getAddress(), device.getName()); mBleMeshManager.setLogger(logSession); final BluetoothDevice bluetoothDevice = device.getDevice(); @@ -354,34 +348,36 @@ private void initIsConnectedLiveData(final boolean connectToNetwork) { */ void disconnect() { clearProvisioningLiveData(); - removeCallbacks(); mIsProvisioningComplete = false; mBleMeshManager.disconnect(); } - void removeCallbacks() { + void clearProvisioningLiveData() { + stopScan(); + mHandler.removeCallbacks(mReconnectRunnable); + mSetupProvisionedNode = false; + mIsReconnectingFlag = false; + mUnprovisionedMeshNodeLiveData.setValue(null); + mProvisionedMeshNodeLiveData.setValue(null); + } + + private void removeCallbacks() { mHandler.removeCallbacksAndMessages(null); } public void identifyNode(final ExtendedBluetoothDevice device) { final UnprovisionedBeacon beacon = (UnprovisionedBeacon) device.getBeacon(); if (beacon != null) { - mMeshManagerApi.identifyNode(beacon.getUuid(), device.getName(), ATTENTION_TIMER); + mMeshManagerApi.identifyNode(beacon.getUuid(), ATTENTION_TIMER); } else { final byte[] serviceData = Utils.getServiceData(device.getScanResult(), BleMeshManager.MESH_PROVISIONING_UUID); if (serviceData != null) { final UUID uuid = mMeshManagerApi.getDeviceUuid(serviceData); - mMeshManagerApi.identifyNode(uuid, device.getName(), ATTENTION_TIMER); + mMeshManagerApi.identifyNode(uuid, ATTENTION_TIMER); } } } - void clearProvisioningLiveData() { - mIsReconnectingFlag = false; - mUnprovisionedMeshNodeLiveData.setValue(null); - mProvisionedMeshNodeLiveData.setValue(null); - } - private void clearExtendedMeshNode() { if (mExtendedMeshNode != null) { mExtendedMeshNode.postValue(null); @@ -392,12 +388,8 @@ LiveData getUnprovisionedMeshNode() { return mUnprovisionedMeshNodeLiveData; } - LiveData getProvisionedMeshNode() { - return mProvisionedMeshNodeLiveData; - } - - LiveData getConnectedMeshNodeAddress() { - return mConnectedMeshNodeAddress; + LiveData getConnectedProxyAddress() { + return mConnectedProxyAddress; } /** @@ -414,11 +406,6 @@ LiveData getSelectedMeshNode() { */ void setSelectedMeshNode(final ProvisionedMeshNode node) { mExtendedMeshNode.postValue(node); - /*if (mExtendedMeshNode == null) { - mExtendedMeshNode = new ExtendedMeshNode(node); - } else { - mExtendedMeshNode.updateMeshNode(node); - }*/ } /** @@ -437,6 +424,38 @@ void setSelectedElement(final Element element) { mSelectedElement.postValue(element); } + /** + * Set the selected model to be configured + * + * @param appKey mesh model + */ + void setSelectedAppKey(@NonNull final ApplicationKey appKey) { + mSelectedAppKey.postValue(appKey); + } + + /** + * Returns the selected mesh model + */ + LiveData getSelectedAppKey() { + return mSelectedAppKey; + } + + /** + * Selects provisioner for editing or adding + * + * @param provisioner {@link Provisioner} + */ + void setSelectedProvisioner(@NonNull final Provisioner provisioner) { + mSelectedProvisioner.postValue(provisioner); + } + + /** + * Returns the selected {@link Provisioner} + */ + LiveData getSelectedProvisioner() { + return mSelectedProvisioner; + } + /** * Returns the selected mesh model */ @@ -478,35 +497,35 @@ public void onDeviceConnected(final BluetoothDevice device) { @Override public void onDeviceDisconnecting(final BluetoothDevice device) { Log.v(TAG, "Disconnecting..."); - if(mIsReconnectingFlag){ + if (mIsReconnectingFlag) { mConnectionState.postValue("Reconnecting..."); } else { mConnectionState.postValue("Disconnecting..."); } } + @SuppressWarnings("ConstantConditions") @Override public void onDeviceDisconnected(final BluetoothDevice device) { Log.v(TAG, "Disconnected"); + mConnectionState.postValue(""); if (mIsReconnectingFlag) { mIsReconnectingFlag = false; mIsReconnecting.postValue(false); mIsConnected.postValue(false); + mIsConnectedToProxy.postValue(false); } else { - mConnectionState.postValue(""); mIsConnected.postValue(false); mIsConnectedToProxy.postValue(false); - if (mConnectedMeshNodeAddress.getValue() != null) { - final ProvisionedMeshNode node = mMeshManagerApi.getMeshNetwork(). - getProvisionedNode(AddressUtils.getUnicastAddressBytes(mConnectedMeshNodeAddress.getValue())); - if (node != null) - node.setProxyFilter(null); + if (mConnectedProxyAddress.getValue() != null) { + final MeshNetwork network = mMeshManagerApi.getMeshNetwork(); + network.setProxyFilter(null); } - clearExtendedMeshNode(); + //clearExtendedMeshNode(); } mSetupProvisionedNode = false; - mConnectedMeshNodeAddress.postValue(null); + mConnectedProxyAddress.postValue(null); } @Override @@ -526,10 +545,20 @@ public void onDeviceReady(final BluetoothDevice device) { if (mBleMeshManager.isProvisioningComplete()) { if (mSetupProvisionedNode) { - //Adding a slight delay here so we don't send anything before we receive the mesh beacon message - final ProvisionedMeshNode node = mProvisionedMeshNodeLiveData.getValue(); - final ConfigCompositionDataGet compositionDataGet = new ConfigCompositionDataGet(); - mHandler.postDelayed(() -> mMeshManagerApi.sendMeshMessage(node.getUnicastAddress(), compositionDataGet), 2000); + if (mMeshNetwork.getSelectedProvisioner().getProvisionerAddress() != null) { + mHandler.postDelayed(() -> { + //Adding a slight delay here so we don't send anything before we receive the mesh beacon message + final ProvisionedMeshNode node = mProvisionedMeshNodeLiveData.getValue(); + if (node != null) { + final ConfigCompositionDataGet compositionDataGet = new ConfigCompositionDataGet(); + mMeshManagerApi.createMeshPdu(node.getUnicastAddress(), compositionDataGet); + } + }, 2000); + } else { + mSetupProvisionedNode = false; + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.PROVISIONER_UNASSIGNED); + clearExtendedMeshNode(); + } } mIsConnectedToProxy.postValue(true); } @@ -628,7 +657,7 @@ public void sendProvisioningPdu(final UnprovisionedMeshNode meshNode, final byte } @Override - public void sendMeshPdu(final byte[] pdu) { + public void onMeshPduCreated(final byte[] pdu) { mBleMeshManager.sendPdu(pdu); } @@ -649,7 +678,7 @@ public void onProvisioningStateChanged(final UnprovisionedMeshNode meshNode, fin mIsProvisioningComplete = false; break; } - mProvisioningStateLiveData.onMeshNodeStateUpdated(state); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.fromStatusCode(state.getState())); } @@ -657,12 +686,10 @@ public void onProvisioningStateChanged(final UnprovisionedMeshNode meshNode, fin public void onProvisioningFailed(final UnprovisionedMeshNode meshNode, final ProvisioningState.States state, final byte[] data) { mUnprovisionedMeshNode = meshNode; mUnprovisionedMeshNodeLiveData.postValue(meshNode); - switch (state) { - case PROVISIONING_FAILED: - mIsProvisioningComplete = false; - break; + if (state == ProvisioningState.States.PROVISIONING_FAILED) { + mIsProvisioningComplete = false; } - mProvisioningStateLiveData.onMeshNodeStateUpdated(state); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.fromStatusCode(state.getState())); } @@ -671,12 +698,10 @@ public void onProvisioningCompleted(final ProvisionedMeshNode meshNode, final Pr mProvisionedMeshNode = meshNode; mUnprovisionedMeshNodeLiveData.postValue(null); mProvisionedMeshNodeLiveData.postValue(meshNode); - switch (state) { - case PROVISIONING_COMPLETE: - onProvisioningCompleted(meshNode); - break; + if (state == ProvisioningState.States.PROVISIONING_COMPLETE) { + onProvisioningCompleted(meshNode); } - mProvisioningStateLiveData.onMeshNodeStateUpdated(state); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.fromStatusCode(state.getState())); } @@ -686,39 +711,33 @@ private void onProvisioningCompleted(final ProvisionedMeshNode node) { mIsReconnecting.postValue(true); mBleMeshManager.disconnect(); mBleMeshManager.refreshDeviceCache(); - mProvisionedNodes.postValue(mMeshNetwork.getProvisionedNodes()); + loadNodes(); mHandler.post(() -> mConnectionState.postValue("Scanning for provisioned node")); mHandler.postDelayed(mReconnectRunnable, 1000); //Added a slight delay to disconnect and refresh the cache } - @Override - public void onTransactionFailed(final byte[] dst, final boolean hasIncompleteTimerExpired) { - mProvisionedMeshNode = mMeshNetwork.getProvisionedNode(dst); - if (mTransactionFailedLiveData.hasActiveObservers()) { - mTransactionFailedLiveData.onTransactionFailed(dst, hasIncompleteTimerExpired); + /** + * Here we load all nodes except the current provisioner. This may contain other provisioner nodes if available + */ + private void loadNodes() { + final List nodes = new ArrayList<>(); + for (final ProvisionedMeshNode node : mMeshNetwork.getNodes()) { + if (!node.getUuid().equalsIgnoreCase(mMeshNetwork.getSelectedProvisioner().getProvisionerUuid())) { + nodes.add(node); + } } + mProvisionedNodes.postValue(nodes); } @Override public void onTransactionFailed(final int dst, final boolean hasIncompleteTimerExpired) { - mProvisionedMeshNode = mMeshNetwork.getProvisionedNode(dst); - if (mTransactionFailedLiveData.hasActiveObservers()) { - mTransactionFailedLiveData.onTransactionFailed(dst, hasIncompleteTimerExpired); - } - } - - @Override - public void onUnknownPduReceived(final byte[] src, final byte[] accessPayload) { - final ProvisionedMeshNode node = mMeshNetwork.getProvisionedNode(src); - if (node != null) { - mProvisionedMeshNode = node; - updateNode(node); - } + mProvisionedMeshNode = mMeshNetwork.getNode(dst); + mTransactionStatus.postValue(new TransactionStatus(dst, hasIncompleteTimerExpired)); } @Override public void onUnknownPduReceived(final int src, final byte[] accessPayload) { - final ProvisionedMeshNode node = mMeshNetwork.getProvisionedNode(src); + final ProvisionedMeshNode node = mMeshNetwork.getNode(src); if (node != null) { mProvisionedMeshNode = node; updateNode(node); @@ -726,85 +745,62 @@ public void onUnknownPduReceived(final int src, final byte[] accessPayload) { } @Override - public void onBlockAcknowledgementSent(final byte[] dst) { - if (dst != null) { - final ProvisionedMeshNode node = mMeshNetwork.getProvisionedNode(dst); - if (node != null) { - mProvisionedMeshNode = node; - if (mSetupProvisionedNode) { - mProvisionedMeshNodeLiveData.postValue(mProvisionedMeshNode); - mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisioningState.States.SENDING_BLOCK_ACKNOWLEDGEMENT); - } - } - } - } - - @Override - public void onBlockAcknowledgementSent(final int dst) { - final ProvisionedMeshNode node = mMeshNetwork.getProvisionedNode(dst); + public void onBlockAcknowledgementProcessed(final int dst, @NonNull final ControlMessage message) { + final ProvisionedMeshNode node = mMeshNetwork.getNode(dst); if (node != null) { mProvisionedMeshNode = node; if (mSetupProvisionedNode) { mProvisionedMeshNodeLiveData.postValue(mProvisionedMeshNode); - mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisioningState.States.SENDING_BLOCK_ACKNOWLEDGEMENT); - } - } - } - - @Override - public void onBlockAcknowledgementReceived(final byte[] src) { - final ProvisionedMeshNode node = mMeshNetwork.getProvisionedNode(src); - if (node != null) { - mProvisionedMeshNode = node; - if (mSetupProvisionedNode) { - mProvisionedMeshNodeLiveData.postValue(node); - mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisioningState.States.BLOCK_ACKNOWLEDGEMENT_RECEIVED); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.SENDING_BLOCK_ACKNOWLEDGEMENT); } } } @Override - public void onBlockAcknowledgementReceived(final int src) { - final ProvisionedMeshNode node = mMeshNetwork.getProvisionedNode(src); + public void onBlockAcknowledgementReceived(final int src, @NonNull final ControlMessage message) { + final ProvisionedMeshNode node = mMeshNetwork.getNode(src); if (node != null) { mProvisionedMeshNode = node; if (mSetupProvisionedNode) { mProvisionedMeshNodeLiveData.postValue(node); - mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisioningState.States.BLOCK_ACKNOWLEDGEMENT_RECEIVED); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.BLOCK_ACKNOWLEDGEMENT_RECEIVED); } } } @Override - public void onMeshMessageSent(final byte[] dst, final MeshMessage meshMessage) { - } - - @Override - public void onMeshMessageSent(final int dst, final MeshMessage meshMessage) { - final ProvisionedMeshNode node = mMeshNetwork.getProvisionedNode(dst); + public void onMeshMessageProcessed(final int dst, @NonNull final MeshMessage meshMessage) { + final ProvisionedMeshNode node = mMeshNetwork.getNode(dst); if (node != null) { mProvisionedMeshNode = node; if (meshMessage instanceof ConfigCompositionDataGet) { if (mSetupProvisionedNode) { mProvisionedMeshNodeLiveData.postValue(node); - mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisioningState.States.COMPOSITION_DATA_GET_SENT); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.COMPOSITION_DATA_GET_SENT); } - } else if (meshMessage instanceof ConfigAppKeyStatus) { + } else if (meshMessage instanceof ConfigDefaultTtlGet) { + if (mSetupProvisionedNode) { + mProvisionedMeshNodeLiveData.postValue(node); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.SENDING_DEFAULT_TTL_GET); + } + } else if (meshMessage instanceof ConfigAppKeyAdd) { + if (mSetupProvisionedNode) { + mProvisionedMeshNodeLiveData.postValue(node); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.SENDING_APP_KEY_ADD); + } + } else if (meshMessage instanceof ConfigNetworkTransmitSet) { if (mSetupProvisionedNode) { mProvisionedMeshNodeLiveData.postValue(node); - mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisioningState.States.SENDING_APP_KEY_ADD); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.SENDING_NETWORK_TRANSMIT_SET); } } } } + @SuppressWarnings("ConstantConditions") @Override - public void onMeshMessageReceived(final byte[] src, final MeshMessage meshMessage) { - } - - @Override - public void onMeshMessageReceived(final int src, final MeshMessage meshMessage) { - final ProvisionedMeshNode node = mMeshNetwork.getProvisionedNode(src); + public void onMeshMessageReceived(final int src, @NonNull final MeshMessage meshMessage) { + final ProvisionedMeshNode node = mMeshNetwork.getNode(src); if (node != null) if (meshMessage instanceof ProxyConfigFilterStatus) { mProvisionedMeshNode = node; @@ -812,38 +808,66 @@ public void onMeshMessageReceived(final int src, final MeshMessage meshMessage) final ProxyConfigFilterStatus status = (ProxyConfigFilterStatus) meshMessage; final int unicastAddress = status.getSrc(); Log.v(TAG, "Proxy configuration source: " + MeshAddress.formatAddress(status.getSrc(), false)); - mConnectedMeshNodeAddress.postValue(unicastAddress); + mConnectedProxyAddress.postValue(unicastAddress); mMeshMessageLiveData.postValue(status); } else if (meshMessage instanceof ConfigCompositionDataStatus) { final ConfigCompositionDataStatus status = (ConfigCompositionDataStatus) meshMessage; if (mSetupProvisionedNode) { mIsCompositionDataReceived = true; mProvisionedMeshNodeLiveData.postValue(node); - mConnectedMeshNodeAddress.postValue(node.getUnicastAddress()); - mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisioningState.States.COMPOSITION_DATA_STATUS_RECEIVED); - //We send app key add after composition is complete. Adding a delay so that we don't send anything before the acknowledgement is sent out. - if (!mMeshNetwork.getAppKeys().isEmpty()) { + mConnectedProxyAddress.postValue(node.getUnicastAddress()); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.COMPOSITION_DATA_STATUS_RECEIVED); + mHandler.postDelayed(() -> { + final ConfigDefaultTtlGet configDefaultTtlGet = new ConfigDefaultTtlGet(); + mMeshManagerApi.createMeshPdu(node.getUnicastAddress(), configDefaultTtlGet); + }, 2500); + } else { + updateNode(node); + } + } else if (meshMessage instanceof ConfigDefaultTtlStatus) { + final ConfigDefaultTtlStatus status = (ConfigDefaultTtlStatus) meshMessage; + if (mSetupProvisionedNode) { + mIsDefaultTtlReceived = true; + mProvisionedMeshNodeLiveData.postValue(node); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.DEFAULT_TTL_STATUS_RECEIVED); + mHandler.postDelayed(() -> { + final ApplicationKey appKey = mMeshNetworkLiveData.getSelectedAppKey(); + final int index = node.getAddedNetKeys().get(0).getIndex(); + final NetworkKey networkKey = mMeshNetwork.getNetKeys().get(index); + final ConfigAppKeyAdd configAppKeyAdd = new ConfigAppKeyAdd(networkKey, appKey); + mMeshManagerApi.createMeshPdu(node.getUnicastAddress(), configAppKeyAdd); + }, 2500); + } else { + updateNode(node); + mMeshMessageLiveData.postValue(status); + } + } else if (meshMessage instanceof ConfigAppKeyStatus) { + final ConfigAppKeyStatus status = (ConfigAppKeyStatus) meshMessage; + if (mSetupProvisionedNode) { + if (status.isSuccessful()) { + mIsAppKeyAddCompleted = true; + mProvisionedMeshNodeLiveData.postValue(node); + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.APP_KEY_STATUS_RECEIVED); mHandler.postDelayed(() -> { - final ApplicationKey appKey = mMeshNetworkLiveData.getSelectedAppKey(); - final ConfigAppKeyAdd configAppKeyAdd = new ConfigAppKeyAdd(node.getAddedNetworkKeys().get(0), appKey); - mMeshManagerApi.sendMeshMessage(node.getUnicastAddress(), configAppKeyAdd); + final ConfigNetworkTransmitSet networkTransmitSet = new ConfigNetworkTransmitSet(2, 1); + mMeshManagerApi.createMeshPdu(node.getUnicastAddress(), networkTransmitSet); }, 2500); } } else { updateNode(node); + mMeshMessageLiveData.postValue(status); } - } else if (meshMessage instanceof ConfigAppKeyStatus) { - final ConfigAppKeyStatus status = (ConfigAppKeyStatus) meshMessage; + } else if (meshMessage instanceof ConfigNetworkTransmitStatus) { if (mSetupProvisionedNode) { - mIsAppKeyAddCompleted = true; mSetupProvisionedNode = false; - mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisioningState.States.APP_KEY_STATUS_RECEIVED); + mIsNetworkRetransmitSetCompleted = true; + mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.NETWORK_TRANSMIT_STATUS_RECEIVED); } else { updateNode(node); + final ConfigNetworkTransmitStatus status = (ConfigNetworkTransmitStatus) meshMessage; mMeshMessageLiveData.postValue(status); } } else if (meshMessage instanceof ConfigModelAppStatus) { - if (updateNode(node)) { final ConfigModelAppStatus status = (ConfigModelAppStatus) meshMessage; final Element element = node.getElements().get(status.getElementAddress()); @@ -882,15 +906,9 @@ public void onMeshMessageReceived(final int src, final MeshMessage meshMessage) final ConfigNodeResetStatus status = (ConfigNodeResetStatus) meshMessage; mExtendedMeshNode.postValue(null); - mProvisionedNodes.postValue(mMeshNetwork.getProvisionedNodes()); + loadNodes(); mMeshMessageLiveData.postValue(status); - } else if (meshMessage instanceof ConfigNetworkTransmitStatus) { - if (updateNode(node)) { - final ConfigNetworkTransmitStatus status = (ConfigNetworkTransmitStatus) meshMessage; - mMeshMessageLiveData.postValue(status); - } - } else if (meshMessage instanceof ConfigRelayStatus) { if (updateNode(node)) { final ConfigRelayStatus status = (ConfigRelayStatus) meshMessage; @@ -965,11 +983,15 @@ private void loadNetwork(final MeshNetwork meshNetwork) { provisioner.setLastSelected(true); mMeshNetwork.selectProvisioner(provisioner); } - //Load live data with mesh network mMeshNetworkLiveData.loadNetworkInformation(meshNetwork); //Load live data with provisioned nodes - mProvisionedNodes.postValue(mMeshNetwork.getProvisionedNodes()); + loadNodes(); + + final ProvisionedMeshNode node = getSelectedMeshNode().getValue(); + if (node != null) { + mExtendedMeshNode.postValue(mMeshNetwork.getNode(node.getUuid())); + } } } @@ -1046,7 +1068,7 @@ public void onScanResult(final int callbackType, final ScanResult result) { } @Override - public void onBatchScanResults(final List results) { + public void onBatchScanResults(@NonNull final List results) { // Batch scan is disabled (report delay = 0) } @@ -1064,42 +1086,17 @@ private void onProvisionedDeviceFound(final ProvisionedMeshNode node, final Exte mHandler.postDelayed(() -> connectToProxy(device), 2000); } - void importMeshNetwork(final Uri uri) { - //We disconnect from the current mesh network before importing one - mBleMeshManager.disconnect(); - mMeshManagerApi.importMeshNetwork(uri); - } - /** * Generates the groups based on the addresses each models have subscribed to */ private void loadGroups() { - final String uuid = mMeshNetwork.getMeshUUID(); - final List groups = new ArrayList<>(); - for (final ProvisionedMeshNode node : mMeshNetwork.getProvisionedNodes()) { - for (Map.Entry elementEntry : node.getElements().entrySet()) { - final Element element = elementEntry.getValue(); - for (Map.Entry modelEntry : element.getMeshModels().entrySet()) { - final MeshModel model = modelEntry.getValue(); - if (model != null) { - final List subscriptionAddresses = model.getSubscribedAddresses(); - for (Integer address : subscriptionAddresses) { - if (!mMeshNetwork.isGroupExist(address)) { - final Group group = new Group(address, uuid); - mMeshNetwork.addGroup(group); - } - } - } - } - } - } mGroups.postValue(mMeshNetwork.getGroups()); } private void updateSelectedGroup() { final Group selectedGroup = mSelectedGroupLiveData.getValue(); if (selectedGroup != null) { - mSelectedGroupLiveData.postValue(mMeshNetwork.getGroup(selectedGroup.getGroupAddress())); + mSelectedGroupLiveData.postValue(mMeshNetwork.getGroup(selectedGroup.getAddress())); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisionerProgress.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisionerProgress.java index c85a96003..1915a8ff0 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisionerProgress.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisionerProgress.java @@ -22,30 +22,33 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; +import no.nordicsemi.android.nrfmeshprovisioner.utils.ProvisionerStates; + public class ProvisionerProgress { - private final ProvisioningStatusLiveData.ProvisioningLiveDataState state; + private final ProvisionerStates state; private int statusReceived; private final String message; private final int resId; - ProvisionerProgress(final ProvisioningStatusLiveData.ProvisioningLiveDataState state, final String message, final int resId){ + ProvisionerProgress(final ProvisionerStates state, final String message, final int resId) { this.state = state; this.message = message; this.resId = resId; } - ProvisionerProgress(final ProvisioningStatusLiveData.ProvisioningLiveDataState state, final int status, final String message, final int resId){ + + ProvisionerProgress(final ProvisionerStates state, final int status, final String message, final int resId) { this.state = state; this.statusReceived = status; this.message = message; this.resId = resId; } - public ProvisioningStatusLiveData.ProvisioningLiveDataState getState(){ + public ProvisionerStates getState() { return state; } - public int getStatusReceived(){ + public int getStatusReceived() { return statusReceived; } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisionersViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisionersViewModel.java new file mode 100644 index 000000000..e2cf8899e --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisionersViewModel.java @@ -0,0 +1,22 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import no.nordicsemi.android.meshprovisioner.Provisioner; +import no.nordicsemi.android.nrfmeshprovisioner.keys.AppKeysActivity; + +/** + * ViewModel for {@link AppKeysActivity} + */ +public class ProvisionersViewModel extends BaseViewModel { + + @Inject + ProvisionersViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); + } + + public void setSelectedProvisioner(@NonNull final Provisioner provisioner) { + mNrfMeshRepository.setSelectedProvisioner(provisioner); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisioningStatusLiveData.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisioningStatusLiveData.java index 900755e73..b9f0d3d3f 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisioningStatusLiveData.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisioningStatusLiveData.java @@ -22,12 +22,11 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; - import java.util.ArrayList; -import no.nordicsemi.android.meshprovisioner.provisionerstates.ProvisioningState; +import androidx.lifecycle.LiveData; import no.nordicsemi.android.nrfmeshprovisioner.R; +import no.nordicsemi.android.nrfmeshprovisioner.utils.ProvisionerStates; public class ProvisioningStatusLiveData extends LiveData { @@ -38,51 +37,6 @@ public void clear() { postValue(this); } - public enum ProvisioningLiveDataState { - PROVISIONING_INVITE(0), - PROVISIONING_CAPABILITIES(1), - PROVISIONING_START(2), - PROVISIONING_PUBLIC_KEY_SENT(3), - PROVISIONING_PUBLIC_KEY_RECEIVED(4), - PROVISIONING_AUTHENTICATION_INPUT_OOB_WAITING(5), - PROVISIONING_AUTHENTICATION_OUTPUT_OOB_WAITING(6), - PROVISIONING_AUTHENTICATION_STATIC_OOB_WAITING(7), - PROVISIONING_AUTHENTICATION_INPUT_ENTERED(8), - PROVISIONING_INPUT_COMPLETE(9), - PROVISIONING_CONFIRMATION_SENT(10), - PROVISIONING_CONFIRMATION_RECEIVED(11), - PROVISIONING_RANDOM_SENT(12), - PROVISIONING_RANDOM_RECEIVED(13), - PROVISIONING_DATA_SENT(14), - PROVISIONING_COMPLETE(15), - PROVISIONING_FAILED(16), - COMPOSITION_DATA_GET_SENT(17), - COMPOSITION_DATA_STATUS_RECEIVED(18), - SENDING_BLOCK_ACKNOWLEDGEMENT(19), - SENDING_APP_KEY_ADD(20), - BLOCK_ACKNOWLEDGEMENT_RECEIVED(21), - APP_KEY_STATUS_RECEIVED(22); - - private final int state; - - ProvisioningLiveDataState(final int state) { - this.state = state; - } - - int getState() { - return state; - } - - static ProvisioningLiveDataState fromStatusCode(final int statusCode) { - for (ProvisioningLiveDataState state : ProvisioningLiveDataState.values()) { - if (state.getState() == statusCode) { - return state; - } - } - throw new IllegalStateException("Invalid state"); - } - } - public ArrayList getStateList() { return mProvisioningProgress; } @@ -94,10 +48,9 @@ public ProvisionerProgress getProvisionerProgress() { return mProvisioningProgress.get(mProvisioningProgress.size() - 1); } - void onMeshNodeStateUpdated(final ProvisioningState.States provisionerState) { + void onMeshNodeStateUpdated(final ProvisionerStates state) { final ProvisionerProgress provisioningProgress; - final ProvisioningLiveDataState state = ProvisioningLiveDataState.fromStatusCode(provisionerState.getState()); - switch (provisionerState) { + switch (state) { case PROVISIONING_INVITE: provisioningProgress = new ProvisionerProgress(state, "Sending provisioning invite...", R.drawable.ic_arrow_forward_black_alpha); mProvisioningProgress.add(provisioningProgress); @@ -169,12 +122,12 @@ void onMeshNodeStateUpdated(final ProvisioningState.States provisionerState) { provisioningProgress = new ProvisionerProgress(state, "Composition data status received...", R.drawable.ic_arrow_back_black_alpha); mProvisioningProgress.add(provisioningProgress); break; - case SENDING_BLOCK_ACKNOWLEDGEMENT: - provisioningProgress = new ProvisionerProgress(state, "Sending block acknowledgements", R.drawable.ic_arrow_forward_black_alpha); + case SENDING_DEFAULT_TTL_GET: + provisioningProgress = new ProvisionerProgress(state, "Sending default TLL get...", R.drawable.ic_arrow_forward_black_alpha); mProvisioningProgress.add(provisioningProgress); break; - case BLOCK_ACKNOWLEDGEMENT_RECEIVED: - provisioningProgress = new ProvisionerProgress(state, "Receiving block acknowledgements", R.drawable.ic_arrow_back_black_alpha); + case DEFAULT_TTL_STATUS_RECEIVED: + provisioningProgress = new ProvisionerProgress(state, "Default TTL status received...", R.drawable.ic_arrow_forward_black_alpha); mProvisioningProgress.add(provisioningProgress); break; case SENDING_APP_KEY_ADD: @@ -185,6 +138,26 @@ void onMeshNodeStateUpdated(final ProvisioningState.States provisionerState) { provisioningProgress = new ProvisionerProgress(state, "App key status received...", R.drawable.ic_arrow_forward_black_alpha); mProvisioningProgress.add(provisioningProgress); break; + case SENDING_NETWORK_TRANSMIT_SET: + provisioningProgress = new ProvisionerProgress(state, "Sending network transmit set...", R.drawable.ic_arrow_forward_black_alpha); + mProvisioningProgress.add(provisioningProgress); + break; + case NETWORK_TRANSMIT_STATUS_RECEIVED: + provisioningProgress = new ProvisionerProgress(state, "Network transmit status received...", R.drawable.ic_arrow_forward_black_alpha); + mProvisioningProgress.add(provisioningProgress); + break; + case SENDING_BLOCK_ACKNOWLEDGEMENT: + provisioningProgress = new ProvisionerProgress(state, "Sending block acknowledgements", R.drawable.ic_arrow_forward_black_alpha); + mProvisioningProgress.add(provisioningProgress); + break; + case BLOCK_ACKNOWLEDGEMENT_RECEIVED: + provisioningProgress = new ProvisionerProgress(state, "Receiving block acknowledgements", R.drawable.ic_arrow_back_black_alpha); + mProvisioningProgress.add(provisioningProgress); + break; + case PROVISIONER_UNASSIGNED: + provisioningProgress = new ProvisionerProgress(state, "Provisioner unassigned...", R.drawable.ic_arrow_forward_black_alpha); + mProvisioningProgress.add(provisioningProgress); + break; } postValue(this); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshProvisionerViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisioningViewModel.java similarity index 54% rename from Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshProvisionerViewModel.java rename to Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisioningViewModel.java index 2c73dc433..3c72c4d87 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/MeshProvisionerViewModel.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ProvisioningViewModel.java @@ -22,25 +22,21 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; -import android.content.Context; - import javax.inject.Inject; -import no.nordicsemi.android.meshprovisioner.MeshManagerApi; +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; import no.nordicsemi.android.meshprovisioner.provisionerstates.UnprovisionedMeshNode; -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; -import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; - -public class MeshProvisionerViewModel extends ViewModel { +import no.nordicsemi.android.nrfmeshprovisioner.ProvisioningActivity; - private final NrfMeshRepository mNrfMeshRepository; +/** + * ViewModel for {@link ProvisioningActivity} + */ +public class ProvisioningViewModel extends BaseViewModel { @Inject - MeshProvisionerViewModel(final NrfMeshRepository nrfMeshRepository) { - this.mNrfMeshRepository = nrfMeshRepository; + ProvisioningViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); } @Override @@ -49,82 +45,60 @@ protected void onCleared() { mNrfMeshRepository.clearProvisioningLiveData(); } - public LiveData isDeviceReady() { - return mNrfMeshRepository.isDeviceReady(); - } - - public LiveData getConnectionState() { - return mNrfMeshRepository.getConnectionState(); - } - - public LiveData isConnected() { - return mNrfMeshRepository.isConnected(); + /** + * Returns the LifeData {@link UnprovisionedMeshNode} + */ + public LiveData getUnprovisionedMeshNode() { + return mNrfMeshRepository.getUnprovisionedMeshNode(); } + /** + * Returns true if reconnecting after provisioning is completed + */ public LiveData isReconnecting() { return mNrfMeshRepository.isReconnecting(); } - public boolean isProvisioningComplete() { - return mNrfMeshRepository.isProvisioningComplete(); - } - - public boolean isCompositionDataStatusReceived() { - return mNrfMeshRepository.isCompositionDataStatusReceived(); - } - - public boolean isAppKeyAddCompleted() { - return mNrfMeshRepository.isAppKeyAddCompleted(); - } - + /** + * Returns the provisioning status + */ public ProvisioningStatusLiveData getProvisioningStatus() { return mNrfMeshRepository.getProvisioningState(); } /** - * Connect to peripheral + * Returns true if provisioning has completed */ - public void connect(final Context context, final ExtendedBluetoothDevice device, final boolean connectToNetwork) { - mNrfMeshRepository.connect(context, device, connectToNetwork); + public boolean isProvisioningComplete() { + return mNrfMeshRepository.isProvisioningComplete(); } /** - * Disconnect from peripheral + * Returns true if the CompositionDataStatus is received */ - public void disconnect() { - mNrfMeshRepository.disconnect(); - } - - public void clearProvisioningCallbacks(){ - disconnect(); - mNrfMeshRepository.removeCallbacks(); - } - - public LiveData getUnProvisionedMeshNode() { - return mNrfMeshRepository.getUnprovisionedMeshNode(); - } - - public LiveData getProvisionedMeshNode() { - return mNrfMeshRepository.getProvisionedMeshNode(); - } - - public void startProvisioning(final UnprovisionedMeshNode node) { - mNrfMeshRepository.getMeshManagerApi().startProvisioningWithStaticOOB(node); + public boolean isCompositionDataStatusReceived() { + return mNrfMeshRepository.isCompositionDataStatusReceived(); } - public MeshNetworkLiveData getMeshNetworkLiveData(){ - return mNrfMeshRepository.getMeshNetworkLiveData(); + /** + * Returns true if the DefaultTTLGet completed + */ + public boolean isDefaultTtlReceived() { + return mNrfMeshRepository.isDefaultTtlReceived(); } - public NrfMeshRepository getNrfMeshRepository() { - return mNrfMeshRepository; + /** + * Returns true if the AppKeyAdd completed + */ + public boolean isAppKeyAddCompleted() { + return mNrfMeshRepository.isAppKeyAddCompleted(); } - public BleMeshManager getBleMeshManager() { - return mNrfMeshRepository.getBleMeshManager(); + /** + * Returns true if the NetworkRetransmitSet is completed + */ + public boolean isNetworkRetransmitSetCompleted() { + return mNrfMeshRepository.isNetworkRetransmitSetCompleted(); } - public MeshManagerApi getMeshManagerApi() { - return mNrfMeshRepository.getMeshManagerApi(); - } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/PublicationViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/PublicationViewModel.java index 6f16b8a7d..8f5da2514 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/PublicationViewModel.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/PublicationViewModel.java @@ -22,61 +22,18 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; - import javax.inject.Inject; -import no.nordicsemi.android.meshprovisioner.MeshManagerApi; -import no.nordicsemi.android.meshprovisioner.transport.Element; -import no.nordicsemi.android.meshprovisioner.transport.MeshModel; -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; -import no.nordicsemi.android.nrfmeshprovisioner.ConfigurationServerActivity; +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.node.PublicationSettingsActivity; /** - * View Model class for {@link ConfigurationServerActivity} + * View Model class for {@link PublicationSettingsActivity} */ -public class PublicationViewModel extends ViewModel { - - private final NrfMeshRepository mNrfMeshRepository; +public class PublicationViewModel extends BaseViewModel { @Inject - PublicationViewModel(final NrfMeshRepository nrfMeshRepository) { - this.mNrfMeshRepository = nrfMeshRepository; - } - /** - * Returns the Mesh repository - */ - public NrfMeshRepository getNrfMeshRepository() { - return mNrfMeshRepository; - } - - /** - * Returns the mesh manager api - */ - public MeshManagerApi getMeshManagerApi() { - return mNrfMeshRepository.getMeshManagerApi(); - } - - - /** - * Get selected mesh node - */ - public LiveData getSelectedMeshNode() { - return mNrfMeshRepository.getSelectedMeshNode(); - } - - /** - * Get selected element - */ - public LiveData getSelectedElement() { - return mNrfMeshRepository.getSelectedElement(); - } - - /** - * Get selected model - */ - public LiveData getSelectedModel() { - return mNrfMeshRepository.getSelectedModel(); + PublicationViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/RangesViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/RangesViewModel.java new file mode 100644 index 000000000..3306a81e9 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/RangesViewModel.java @@ -0,0 +1,28 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import no.nordicsemi.android.meshprovisioner.Provisioner; + +public class RangesViewModel extends BaseViewModel { + + /** + * Constructs {@link BaseViewModel} + * + * @param nRfMeshRepository Mesh Repository {@link NrfMeshRepository} + */ + @Inject + RangesViewModel(@NonNull final NrfMeshRepository nRfMeshRepository) { + super(nRfMeshRepository); + } + + public LiveData getSelectedProvisioner() { + return mNrfMeshRepository.getSelectedProvisioner(); + } + + public void setSelectedProvisioner(@NonNull final Provisioner provisioner) { + mNrfMeshRepository.setSelectedProvisioner(provisioner); + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ReconnectViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ReconnectViewModel.java index 5809e7df5..8399f09a0 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ReconnectViewModel.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ReconnectViewModel.java @@ -22,54 +22,19 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; -import android.content.Context; +import androidx.annotation.NonNull; import javax.inject.Inject; -import no.nordicsemi.android.meshprovisioner.MeshManagerApi; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; -import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; +import no.nordicsemi.android.nrfmeshprovisioner.ble.ReconnectActivity; -public class ReconnectViewModel extends ViewModel { - - private final NrfMeshRepository mNrfMeshRepository; +/** + * ViewModel for {@link ReconnectActivity} + */ +public class ReconnectViewModel extends BaseViewModel { @Inject - ReconnectViewModel(final NrfMeshRepository nrfMeshRepository) { - this.mNrfMeshRepository = nrfMeshRepository; - } - - public LiveData isDeviceReady() { - return mNrfMeshRepository.isDeviceReady(); - } - - public LiveData getConnectionState() { - return mNrfMeshRepository.getConnectionState(); - } - - public LiveData isConnected() { - return mNrfMeshRepository.isConnected(); - } - - public void connect(final Context context, final ExtendedBluetoothDevice device, final boolean connectToNetwork) { - mNrfMeshRepository.connect(context, device, connectToNetwork); - } - - public void disconnect() { - mNrfMeshRepository.disconnect(); - } - - public NrfMeshRepository getNrfMeshRepository() { - return mNrfMeshRepository; - } - - public BleMeshManager getBleMeshManager() { - return mNrfMeshRepository.getBleMeshManager(); - } - - public MeshManagerApi getMeshManagerApi() { - return mNrfMeshRepository.getMeshManagerApi(); + ReconnectViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerLiveData.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerLiveData.java index 42d042c39..a38e081c0 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerLiveData.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerLiveData.java @@ -22,9 +22,9 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerRepository.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerRepository.java index 13a7727e9..a5ecec6ff 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerRepository.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerRepository.java @@ -37,14 +37,16 @@ import javax.inject.Inject; +import androidx.annotation.NonNull; import no.nordicsemi.android.meshprovisioner.MeshManagerApi; -import no.nordicsemi.android.meshprovisioner.transport.NetworkKey; +import no.nordicsemi.android.meshprovisioner.MeshNetwork; import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; import no.nordicsemi.android.nrfmeshprovisioner.utils.Utils; import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat; import no.nordicsemi.android.support.v18.scanner.ScanCallback; import no.nordicsemi.android.support.v18.scanner.ScanFilter; +import no.nordicsemi.android.support.v18.scanner.ScanRecord; import no.nordicsemi.android.support.v18.scanner.ScanResult; import no.nordicsemi.android.support.v18.scanner.ScanSettings; @@ -68,7 +70,7 @@ public class ScannerRepository { private final ScanCallback mScanCallbacks = new ScanCallback() { @Override - public void onScanResult(final int callbackType, final ScanResult result) { + public void onScanResult(final int callbackType, @NonNull final ScanResult result) { try { if (mFilterUuid.equals(BleMeshManager.MESH_PROVISIONING_UUID)) { // If the packet has been obtained while Location was disabled, mark Location as not required @@ -98,7 +100,7 @@ public void onScanResult(final int callbackType, final ScanResult result) { } @Override - public void onBatchScanResults(final List results) { + public void onBatchScanResults(@NonNull final List results) { // Batch scan is disabled (report delay = 0) } @@ -158,16 +160,20 @@ public ScannerLiveData getScannerState() { return mScannerLiveData; } - private void updateScannerLiveData(final ScanResult result){ - if (result.getScanRecord() != null) { - final byte[] beaconData = mMeshManagerApi.getMeshBeaconData(result.getScanRecord().getBytes()); - if (beaconData != null) { - mScannerLiveData.deviceDiscovered(result, mMeshManagerApi.getMeshBeacon(beaconData)); - } else { - mScannerLiveData.deviceDiscovered(result); + private void updateScannerLiveData(final ScanResult result) { + final ScanRecord scanRecord = result.getScanRecord(); + if (scanRecord != null) { + if (scanRecord.getBytes() != null) { + final byte[] beaconData = mMeshManagerApi.getMeshBeaconData(scanRecord.getBytes()); + if (beaconData != null) { + mScannerLiveData.deviceDiscovered(result, mMeshManagerApi.getMeshBeacon(beaconData)); + } else { + mScannerLiveData.deviceDiscovered(result); + } } } } + /** * Register for required broadcast receivers. */ @@ -207,9 +213,11 @@ public void startScan(final UUID filterUuid) { } if (mFilterUuid.equals(BleMeshManager.MESH_PROXY_UUID)) { - final List networkKeys = mMeshManagerApi.getMeshNetwork().getNetKeys(); - if (!networkKeys.isEmpty()) { - mNetworkId = mMeshManagerApi.generateNetworkId(networkKeys.get(0).getKey()); + final MeshNetwork network = mMeshManagerApi.getMeshNetwork(); + if (network != null) { + if (!network.getNetKeys().isEmpty()) { + mNetworkId = mMeshManagerApi.generateNetworkId(network.getNetKeys().get(0).getKey()); + } } } @@ -250,9 +258,12 @@ public void stopScan() { * @return true if the node identity matches or false otherwise */ private boolean checkIfNodeIdentityMatches(final byte[] serviceData) { - for (ProvisionedMeshNode node : mMeshManagerApi.getMeshNetwork().getProvisionedNodes()) { - if (mMeshManagerApi.nodeIdentityMatches(node, serviceData)) { - return true; + final MeshNetwork network = mMeshManagerApi.getMeshNetwork(); + if (network != null) { + for (ProvisionedMeshNode node : network.getNodes()) { + if (mMeshManagerApi.nodeIdentityMatches(node, serviceData)) { + return true; + } } } return false; diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerViewModel.java index 355da3a32..670b4c717 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerViewModel.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ScannerViewModel.java @@ -22,24 +22,21 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; -import android.content.Context; - import javax.inject.Inject; -import no.nordicsemi.android.meshprovisioner.MeshManagerApi; -import no.nordicsemi.android.nrfmeshprovisioner.adapter.ExtendedBluetoothDevice; -import no.nordicsemi.android.nrfmeshprovisioner.ble.BleMeshManager; +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.ble.ScannerActivity; -public class ScannerViewModel extends ViewModel { +/** + * ViewModel for {@link ScannerActivity} + */ +public class ScannerViewModel extends BaseViewModel { - private final NrfMeshRepository mNrfMeshRepository; private final ScannerRepository mScannerRepository; @Inject - ScannerViewModel(final NrfMeshRepository nrfMeshRepository, final ScannerRepository scannerRepository) { - this.mNrfMeshRepository = nrfMeshRepository; + ScannerViewModel(@NonNull final NrfMeshRepository nrfMeshRepository, @NonNull final ScannerRepository scannerRepository) { + super(nrfMeshRepository); this.mScannerRepository = scannerRepository; scannerRepository.registerBroadcastReceivers(); } @@ -50,38 +47,6 @@ protected void onCleared() { mScannerRepository.unregisterBroadcastReceivers(); } - public LiveData isDeviceReady() { - return mNrfMeshRepository.isDeviceReady(); - } - - public LiveData getConnectionState() { - return mNrfMeshRepository.getConnectionState(); - } - - public LiveData isConnected() { - return mNrfMeshRepository.isConnected(); - } - - public void connect(final Context context, final ExtendedBluetoothDevice device, final boolean connectToNetwork) { - mNrfMeshRepository.connect(context, device, connectToNetwork); - } - - public void disconnect() { - mNrfMeshRepository.disconnect(); - } - - public NrfMeshRepository getNrfMeshRepository() { - return mNrfMeshRepository; - } - - public BleMeshManager getBleMeshManager() { - return mNrfMeshRepository.getBleMeshManager(); - } - - public MeshManagerApi getMeshManagerApi() { - return mNrfMeshRepository.getMeshManagerApi(); - } - /** * Returns an instance of the scanner repository */ diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SharedViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SharedViewModel.java index 028810d51..f5e53437c 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SharedViewModel.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SharedViewModel.java @@ -22,124 +22,56 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; -import android.net.Uri; - -import java.util.List; - import javax.inject.Inject; -import no.nordicsemi.android.meshprovisioner.Group; -import no.nordicsemi.android.meshprovisioner.MeshManagerApi; -import no.nordicsemi.android.meshprovisioner.transport.ProvisionedMeshNode; +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import no.nordicsemi.android.nrfmeshprovisioner.GroupsFragment; +import no.nordicsemi.android.nrfmeshprovisioner.NetworkFragment; +import no.nordicsemi.android.nrfmeshprovisioner.ProxyFilterFragment; +import no.nordicsemi.android.nrfmeshprovisioner.SettingsFragment; -public class SharedViewModel extends ViewModel { +/** + * ViewModel for {@link NetworkFragment}, {@link GroupsFragment}, {@link ProxyFilterFragment}, {@link SettingsFragment} + */ +public class SharedViewModel extends BaseViewModel { private final ScannerRepository mScannerRepository; - private final NrfMeshRepository nRFMeshRepository; @Inject - SharedViewModel(final ScannerRepository scannerRepository, final NrfMeshRepository nrfMeshRepository) { + SharedViewModel(@NonNull final NrfMeshRepository nrfMeshRepository, @NonNull final ScannerRepository scannerRepository) { + super(nrfMeshRepository); mScannerRepository = scannerRepository; - nRFMeshRepository = nrfMeshRepository; scannerRepository.registerBroadcastReceivers(); } @Override protected void onCleared() { super.onCleared(); - nRFMeshRepository.disconnect(); + mNrfMeshRepository.disconnect(); mScannerRepository.unregisterBroadcastReceivers(); } - public MeshManagerApi getMeshManagerApi(){ - return nRFMeshRepository.getMeshManagerApi(); - } - - public void importMeshNetwork(final Uri uri){ - nRFMeshRepository.importMeshNetwork(uri); - } - - public MeshNetworkLiveData getMeshNetworkLiveData() { - return nRFMeshRepository.getMeshNetworkLiveData(); - } - - public LiveData> getGroups(){ - return nRFMeshRepository.getGroups(); - } - - /** - * Returns an instance of the scanner repository - */ - public ScannerRepository getScannerRepository() { - return mScannerRepository; - } - - public NrfMeshRepository getnRFMeshRepository() { - return nRFMeshRepository; - } - - /** - * Returns the provisioned nodes as a live data object. - */ - public LiveData> getProvisionedNodes() { - return nRFMeshRepository.getProvisionedNodes(); - } - - /** - * Returns if currently connected to a peripheral device. - * - * @return true if connected and false otherwise - */ - public LiveData isConnected() { - return nRFMeshRepository.isConnected(); - } - /** - * Disconnect from peripheral + * Returns network load state */ - public void disconnect() { - nRFMeshRepository.disconnect(); + public LiveData getNetworkLoadState() { + return mNrfMeshRepository.getNetworkLoadState(); } /** - * Returns if currently connected to the mesh network. - * - * @return true if connected and false otherwise + * Returns network export state */ - public LiveData isConnectedToProxy() { - return nRFMeshRepository.isConnectedToProxy(); + public LiveData getNetworkExportState() { + return mNrfMeshRepository.getNetworkExportState(); } /** - * Set the mesh node to be configured + * Sets the selected group * - * @param meshNode provisioned mesh node + * @param address Address of the group */ - public void setSelectedMeshNode(final ProvisionedMeshNode meshNode) { - nRFMeshRepository.setSelectedMeshNode(meshNode); - } - - /** - * Reset mesh network - */ - public void resetMeshNetwork() { - nRFMeshRepository.resetMeshNetwork(); - } - - public LiveData getNetworkLoadState(){ - return nRFMeshRepository.getNetworkLoadState(); - } - public LiveData getNetworkExportState(){ - return nRFMeshRepository.getNetworkExportState(); - } - - public LiveData getConnectedMeshNodeAddress(){ - return nRFMeshRepository.getConnectedMeshNodeAddress(); - } - - public void setSelectedGroup(final int address){ - nRFMeshRepository.setSelectedGroup(address); + public void setSelectedGroup(final int address) { + mNrfMeshRepository.setSelectedGroup(address); } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SingleLiveEvent.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SingleLiveEvent.java index 4d8f13c1e..43adbf02d 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SingleLiveEvent.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SingleLiveEvent.java @@ -22,11 +22,12 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.LifecycleOwner; -import android.arch.lifecycle.MutableLiveData; -import android.arch.lifecycle.Observer; -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; +import androidx.annotation.MainThread; +import androidx.annotation.Nullable; import android.util.Log; import java.util.concurrent.atomic.AtomicBoolean; @@ -48,7 +49,7 @@ class SingleLiveEvent extends MutableLiveData { private final AtomicBoolean mPending = new AtomicBoolean(false); @MainThread - public void observe(LifecycleOwner owner, final Observer observer) { + public void observe(@NonNull final LifecycleOwner owner, @NonNull final Observer observer) { if (hasActiveObservers()) { Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SplashViewModel.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SplashViewModel.java index 38ff1c3b6..5a9872a4a 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SplashViewModel.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/SplashViewModel.java @@ -22,19 +22,19 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.ViewModel; +import androidx.annotation.NonNull; +import no.nordicsemi.android.nrfmeshprovisioner.SplashScreenActivity; import javax.inject.Inject; -import no.nordicsemi.android.meshprovisioner.MeshManagerApi; - -public class SplashViewModel extends ViewModel { - - private final NrfMeshRepository mNrfMeshRepository; +/** + * ViewModel for {@link SplashScreenActivity} + */ +public class SplashViewModel extends BaseViewModel { @Inject - SplashViewModel(final NrfMeshRepository nrfMeshRepository) { - this.mNrfMeshRepository = nrfMeshRepository; + SplashViewModel(@NonNull final NrfMeshRepository nrfMeshRepository) { + super(nrfMeshRepository); } @Override @@ -42,15 +42,4 @@ protected void onCleared() { super.onCleared(); } - public NrfMeshRepository getNrfMeshRepository() { - return mNrfMeshRepository; - } - - public MeshManagerApi getMeshManagerApi() { - return mNrfMeshRepository.getMeshManagerApi(); - } - - public MeshNetworkLiveData getMeshNetworkLiveData(){ - return mNrfMeshRepository.getMeshNetworkLiveData(); - } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/TransactionStatus.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/TransactionStatus.java new file mode 100644 index 000000000..c596f71bd --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/TransactionStatus.java @@ -0,0 +1,27 @@ +package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; + +public class TransactionStatus { + + private int mElementAddress; + private boolean incompleteTimerExpired; + + TransactionStatus(final int elementAddress, final boolean hasIncompleteTimerExpired) { + this.mElementAddress = elementAddress; + incompleteTimerExpired = hasIncompleteTimerExpired; + } + + /** + * Returns the element address of the failed transaction + */ + @SuppressWarnings("unused") + public int getElementAddress() { + return mElementAddress; + } + + /** + * Returns if incomplete timer expired of the failed transaction + */ + public boolean isIncompleteTimerExpired() { + return incompleteTimerExpired; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/TransactionStatusLiveData.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/TransactionStatusLiveData.java deleted file mode 100644 index 439a5d33f..000000000 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/TransactionStatusLiveData.java +++ /dev/null @@ -1,31 +0,0 @@ -package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; - -import no.nordicsemi.android.meshprovisioner.utils.MeshParserUtils; - -@SuppressWarnings("unchecked") -public class TransactionStatusLiveData extends SingleLiveEvent { - - private int mElementAddress; - private boolean incompleteTimerExpired; - - TransactionStatusLiveData() { - } - - void onTransactionFailed(final byte[] elementAddress, final boolean hasIncompleteTimerExpired) { - this.mElementAddress = MeshParserUtils.bytesToInt(elementAddress); - incompleteTimerExpired = hasIncompleteTimerExpired; - } - - void onTransactionFailed(final int elementAddress, final boolean hasIncompleteTimerExpired) { - this.mElementAddress = elementAddress; - incompleteTimerExpired = hasIncompleteTimerExpired; - } - - public int getElementAddress() { - return mElementAddress; - } - - public boolean isIncompleteTimerExpired() { - return incompleteTimerExpired; - } -} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ViewModelFactory.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ViewModelFactory.java index 86c14d8a0..53001c6ed 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ViewModelFactory.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/viewmodels/ViewModelFactory.java @@ -22,59 +22,69 @@ package no.nordicsemi.android.nrfmeshprovisioner.viewmodels; -import android.arch.lifecycle.ViewModel; -import android.arch.lifecycle.ViewModelProvider; -import android.support.annotation.NonNull; - import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import javax.inject.Inject; +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; import no.nordicsemi.android.nrfmeshprovisioner.di.ViewModelSubComponent; public class ViewModelFactory implements ViewModelProvider.Factory { - private final Map> creators; + private final Map> creators; - @Inject - public ViewModelFactory(final ViewModelSubComponent viewModelSubComponent) { - creators = new HashMap<>(); - // we cannot inject view models directly because they won't be bound to the owner's - // view model scope. - creators.put(SplashViewModel.class, viewModelSubComponent::splashViewModel); - creators.put(SharedViewModel.class, viewModelSubComponent::commonViewModel); - creators.put(ScannerViewModel.class, viewModelSubComponent::scannerViewModel); - creators.put(GroupControlsViewModel.class, viewModelSubComponent::groupControlsViewModel); - creators.put(ManageAppKeysViewModel.class, viewModelSubComponent::manageAppKeysViewModel); - creators.put(MeshProvisionerViewModel.class, viewModelSubComponent::meshProvisionerViewModel); - creators.put(NodeConfigurationViewModel.class, viewModelSubComponent::meshConfigurationViewModel); - creators.put(ModelConfigurationViewModel.class, viewModelSubComponent::modelConfigurationViewModel); - creators.put(PublicationViewModel.class, viewModelSubComponent::publicationViewModel); - creators.put(ReconnectViewModel.class, viewModelSubComponent::reconnectViewModule); - } + @Inject + public ViewModelFactory(final ViewModelSubComponent viewModelSubComponent) { + creators = new HashMap<>(); + // we cannot inject view models directly because they won't be bound to the owner's + // view model scope. + creators.put(SplashViewModel.class, viewModelSubComponent::splashViewModel); + creators.put(SharedViewModel.class, viewModelSubComponent::commonViewModel); + creators.put(ScannerViewModel.class, viewModelSubComponent::scannerViewModel); + creators.put(GroupControlsViewModel.class, viewModelSubComponent::groupControlsViewModel); + creators.put(ProvisionersViewModel.class, viewModelSubComponent::provisionersViewModel); + creators.put(AddProvisionerViewModel.class, viewModelSubComponent::addProvisionerViewModel); + creators.put(EditProvisionerViewModel.class, viewModelSubComponent::editProvisionerViewModel); + creators.put(RangesViewModel.class, viewModelSubComponent::rangesViewModel); + creators.put(NetKeysViewModel.class, viewModelSubComponent::netKeysViewModel); + creators.put(AddNetKeyViewModel.class, viewModelSubComponent::addNetKeyViewModel); + creators.put(EditNetKeyViewModel.class, viewModelSubComponent::editNetKeyViewModel); + creators.put(AppKeysViewModel.class, viewModelSubComponent::appKeysViewModel); + creators.put(AddAppKeyViewModel.class, viewModelSubComponent::addAppKeyViewModel); + creators.put(EditAppKeyViewModel.class, viewModelSubComponent::editAppKeyViewModel); + creators.put(ProvisioningViewModel.class, viewModelSubComponent::meshProvisionerViewModel); + creators.put(NodeDetailsViewModel.class, viewModelSubComponent::nodeDetailsViewModel); + creators.put(NodeConfigurationViewModel.class, viewModelSubComponent::nodeConfigurationViewModel); + creators.put(AddKeysViewModel.class, viewModelSubComponent::addKeysViewModel); + creators.put(ModelConfigurationViewModel.class, viewModelSubComponent::modelConfigurationViewModel); + creators.put(PublicationViewModel.class, viewModelSubComponent::publicationViewModel); + creators.put(ReconnectViewModel.class, viewModelSubComponent::reconnectViewModule); + } - @SuppressWarnings("unchecked") - @NonNull - @Override - public T create(@NonNull final Class modelClass) { - Callable creator = creators.get(modelClass); - if (creator == null) { - for (Map.Entry> entry : creators.entrySet()) { - if (modelClass.isAssignableFrom(entry.getKey())) { - creator = entry.getValue(); - break; - } - } - } - if (creator == null) { - throw new IllegalArgumentException("unknown model class " + modelClass); - } - try { - return (T) creator.call(); - } catch (final Exception e) { - throw new RuntimeException(e); - } - } + @SuppressWarnings("unchecked") + @NonNull + @Override + public T create(@NonNull final Class modelClass) { + Callable creator = creators.get(modelClass); + if (creator == null) { + for (Map.Entry> entry : creators.entrySet()) { + if (modelClass.isAssignableFrom(entry.getKey())) { + creator = entry.getValue(); + break; + } + } + } + if (creator == null) { + throw new IllegalArgumentException("unknown model class " + modelClass); + } + try { + return (T) creator.call(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/ItemTouchHelperAdapter.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/ItemTouchHelperAdapter.java index 180151c59..6acbb2060 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/ItemTouchHelperAdapter.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/ItemTouchHelperAdapter.java @@ -22,8 +22,8 @@ package no.nordicsemi.android.nrfmeshprovisioner.widgets; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.ItemTouchHelper; /** * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}. diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RangeView.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RangeView.java new file mode 100644 index 000000000..2b79018a1 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RangeView.java @@ -0,0 +1,190 @@ +package no.nordicsemi.android.nrfmeshprovisioner.widgets; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import no.nordicsemi.android.meshprovisioner.AllocatedGroupRange; +import no.nordicsemi.android.meshprovisioner.AllocatedSceneRange; +import no.nordicsemi.android.meshprovisioner.AllocatedUnicastRange; +import no.nordicsemi.android.meshprovisioner.Range; +import no.nordicsemi.android.meshprovisioner.utils.MeshAddress; +import no.nordicsemi.android.nrfmeshprovisioner.R; + +public class RangeView extends View { + + private Paint mPaint; + private int unallocatedColor; + private int rangesColor; + private int otherRangeColor; + private int conflictColor; + private int borderColor; + private List ranges = new ArrayList<>(); + private List otherRanges = new ArrayList<>(); + + public RangeView(@NonNull final Context context, @Nullable final AttributeSet attrs) { + super(context, attrs); + initPaint(context); + } + + public void addRanges(@NonNull final List ranges) { + this.ranges.addAll(ranges); + invalidate(); + } + + public void addRange(@NonNull final Range range) { + ranges.add(range); + invalidate(); + } + + public void clearRanges() { + ranges.clear(); + invalidate(); + } + + public void addOtherRanges(@NonNull final List otherRanges) { + this.otherRanges.addAll(otherRanges); + invalidate(); + } + + public void addOtherRange(@NonNull final Range otherRange) { + otherRanges.add(otherRange); + invalidate(); + } + + public void clearOtherRanges() { + otherRanges.clear(); + invalidate(); + } + + @Override + protected void onDraw(final Canvas canvas) { + super.onDraw(canvas); + canvas.drawColor(unallocatedColor); + drawRanges(canvas); + drawOtherRanges(canvas); + drawConflictingRange(canvas); + drawBorder(canvas); + } + + @Override + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private void initPaint(@NonNull final Context context) { + unallocatedColor = ContextCompat.getColor(context, R.color.nordicLightGray); + rangesColor = ContextCompat.getColor(context, R.color.nordicLake); + otherRangeColor = ContextCompat.getColor(context, R.color.nordicMediumGray); + conflictColor = ContextCompat.getColor(context, R.color.nordicRed); + borderColor = ContextCompat.getColor(context, R.color.nordicMediumGray); + mPaint = new Paint(); + } + + private void drawRanges(@NonNull final Canvas canvas) { + final Paint paint = getRectPaint(); + paint.setColor(rangesColor); + for (Range range : ranges) { + if (range instanceof AllocatedUnicastRange) { + final AllocatedUnicastRange range1 = (AllocatedUnicastRange) range; + final Rect rect = getRegion(canvas, range1.getLowAddress(), range1.getHighAddress(), MeshAddress.START_UNICAST_ADDRESS, MeshAddress.END_UNICAST_ADDRESS); + canvas.drawRect(rect, paint); + } else if (range instanceof AllocatedGroupRange) { + final AllocatedGroupRange range1 = (AllocatedGroupRange) range; + final Rect rect = getRegion(canvas, range1.getLowAddress(), range1.getHighAddress(), MeshAddress.START_GROUP_ADDRESS, MeshAddress.END_GROUP_ADDRESS); + canvas.drawRect(rect, paint); + } else if (range instanceof AllocatedSceneRange) { + final AllocatedSceneRange range1 = (AllocatedSceneRange) range; + final Rect rect = getRegion(canvas, range1.getFirstScene(), range1.getLastScene(), range1.getLowerBound(), range1.getUpperBound()); + canvas.drawRect(rect, paint); + } + } + } + + private void drawOtherRanges(@NonNull final Canvas canvas) { + final Paint paint = getRectPaint(); + paint.setColor(otherRangeColor); + for (Range range : otherRanges) { + if (range instanceof AllocatedUnicastRange) { + final AllocatedUnicastRange range1 = (AllocatedUnicastRange) range; + final Rect rect = getRegion(canvas, range1.getLowAddress(), range1.getHighAddress(), MeshAddress.START_UNICAST_ADDRESS, MeshAddress.END_UNICAST_ADDRESS); + canvas.drawRect(rect, paint); + } else if (range instanceof AllocatedGroupRange) { + final AllocatedGroupRange range1 = (AllocatedGroupRange) range; + final Rect rect = getRegion(canvas, range1.getLowAddress(), range1.getHighAddress(), MeshAddress.START_GROUP_ADDRESS, MeshAddress.END_GROUP_ADDRESS); + canvas.drawRect(rect, paint); + } else if (range instanceof AllocatedSceneRange) { + final AllocatedSceneRange range1 = (AllocatedSceneRange) range; + final Rect rect = getRegion(canvas, range1.getFirstScene(), range1.getLastScene(), range1.getLowerBound(), range1.getUpperBound()); + canvas.drawRect(rect, paint); + } + } + } + + public void drawConflictingRange(@NonNull final Canvas canvas) { + final Paint paint = getRectPaint(); + paint.setColor(conflictColor); + for (Range range : ranges) { + for (Range other : otherRanges) { + if (range instanceof AllocatedUnicastRange) { + final AllocatedUnicastRange unicastRange = (AllocatedUnicastRange) range; + final AllocatedUnicastRange otherRange = (AllocatedUnicastRange) other; + if (unicastRange.overlaps(otherRange)) { + final Rect overlapRegion = getRegion(canvas, otherRange.getLowAddress(), otherRange.getHighAddress(), + MeshAddress.START_UNICAST_ADDRESS, MeshAddress.END_UNICAST_ADDRESS); + canvas.drawRect(overlapRegion, paint); + } + } else if (range instanceof AllocatedGroupRange) { + final AllocatedGroupRange groupRange = (AllocatedGroupRange) range; + final AllocatedGroupRange otherRange = (AllocatedGroupRange) other; + if (groupRange.overlaps(otherRange)) { + final Rect overlapRegion = getRegion(canvas, otherRange.getLowAddress(), otherRange.getHighAddress(), + MeshAddress.START_GROUP_ADDRESS, MeshAddress.END_GROUP_ADDRESS); + canvas.drawRect(overlapRegion, paint); + } + } else { + final AllocatedSceneRange sceneRange = (AllocatedSceneRange) range; + final AllocatedSceneRange otherRange = (AllocatedSceneRange) other; + if (sceneRange.overlaps(otherRange)) { + final Rect overlapRegion = getRegion(canvas, otherRange.getFirstScene(), otherRange.getLastScene(), + otherRange.getLowerBound(), otherRange.getUpperBound()); + canvas.drawRect(overlapRegion, paint); + } + } + } + } + } + + private void drawBorder(@NonNull final Canvas canvas) { + mPaint.setColor(borderColor); + mPaint.setStrokeWidth(2); + mPaint.setStyle(Paint.Style.STROKE); + canvas.drawRect(canvas.getClipBounds(), mPaint); + } + + @NonNull + private Rect getRegion(@NonNull final Canvas canvas, final int lowAddress, final int highAddress, final int lowerBound, final int upperBound) { + final Rect mRect = canvas.getClipBounds(); + final float unit = (mRect.width() / (float) (upperBound - lowerBound)); + final int x = (int) ((lowAddress - lowerBound) * unit); + final int right = (int) ((highAddress - lowerBound) * unit); + return new Rect(x, 0, right, mRect.height()); + } + + @NonNull + private Paint getRectPaint() { + final Paint p = mPaint; + p.setAntiAlias(true); + p.setStyle(Paint.Style.FILL); + return p; + } +} diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RemovableItemTouchHelperCallback.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RemovableItemTouchHelperCallback.java index a2e88c522..0c97b300c 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RemovableItemTouchHelperCallback.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RemovableItemTouchHelperCallback.java @@ -23,16 +23,17 @@ package no.nordicsemi.android.nrfmeshprovisioner.widgets; import android.graphics.Canvas; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; import android.view.Gravity; import android.widget.FrameLayout; import android.widget.ImageView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; import no.nordicsemi.android.nrfmeshprovisioner.R; import no.nordicsemi.android.nrfmeshprovisioner.adapter.GroupAdapter; +import no.nordicsemi.android.nrfmeshprovisioner.node.adapter.NodeAdapter; /** * This callback works with {@link RemovableViewHolder}. Only view holders that inherit from this class may be removed. @@ -93,7 +94,11 @@ public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionStat } } - public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + @Override + public void onChildDraw(@NonNull Canvas c, + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + float dX, float dY, int actionState, boolean isCurrentlyActive) { if (recyclerView.getId() == R.id.recycler_view_groups) { final GroupAdapter adapter = (GroupAdapter) recyclerView.getAdapter(); if (adapter != null) { @@ -104,7 +109,6 @@ public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @ swipeBack = true; } else { if (dX == 0 && !swipeBack) { - Log.v("DX", "DX" + dX); mAdapter.onItemDismissFailed((RemovableViewHolder) viewHolder); } } @@ -112,6 +116,18 @@ public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @ getDefaultUIUtil().onDraw(c, recyclerView, ((RemovableViewHolder) viewHolder).getSwipeableView(), dX, dY, actionState, isCurrentlyActive); } } + } else if (recyclerView.getId() == R.id.recycler_view_provisioned_nodes) { + final NodeAdapter adapter = (NodeAdapter) recyclerView.getAdapter(); + if (adapter != null) { + if (dX > -800.0f && dX < 800.0f && dX != 0) { + getDefaultUIUtil().onDraw(c, recyclerView, ((RemovableViewHolder) viewHolder).getSwipeableView(), dX, dY, actionState, isCurrentlyActive); + //swipeBack = true; + } else { + if (dX == 0 && !swipeBack) { + mAdapter.onItemDismissFailed((RemovableViewHolder) viewHolder); + } + } + } } else { getDefaultUIUtil().onDraw(c, recyclerView, ((RemovableViewHolder) viewHolder).getSwipeableView(), dX, dY, actionState, isCurrentlyActive); } diff --git a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RemovableViewHolder.java b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RemovableViewHolder.java index c2d046c67..9f8edc4ad 100644 --- a/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RemovableViewHolder.java +++ b/Example/nrf-mesh/app/src/main/java/no/nordicsemi/android/nrfmeshprovisioner/widgets/RemovableViewHolder.java @@ -22,7 +22,7 @@ package no.nordicsemi.android.nrfmeshprovisioner.widgets; -import android.support.v7.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView; import android.view.View; import android.widget.ImageView; diff --git a/Example/nrf-mesh/app/src/main/res/color/filter_add_button_states.xml b/Example/nrf-mesh/app/src/main/res/color/filter_add_button_states.xml new file mode 100644 index 000000000..612062f35 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/color/filter_add_button_states.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/color/proxy_filter_button_states.xml b/Example/nrf-mesh/app/src/main/res/color/proxy_filter_button_states.xml new file mode 100644 index 000000000..941101eba --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/color/proxy_filter_button_states.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/drawable/blue_button_states.xml b/Example/nrf-mesh/app/src/main/res/drawable/blue_button_states.xml new file mode 100644 index 000000000..8d72e59e4 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/blue_button_states.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_account_check.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_account_check.xml new file mode 100644 index 000000000..99d1be5d5 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_account_check.xml @@ -0,0 +1,5 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_account_key_black_24dp_alpha.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_account_key_black_alpha_24dp.xml similarity index 100% rename from Example/nrf-mesh/app/src/main/res/drawable/ic_account_key_black_24dp_alpha.xml rename to Example/nrf-mesh/app/src/main/res/drawable/ic_account_key_black_alpha_24dp.xml diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_arrow_collapse_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_arrow_collapse_black_alpha_24dp.xml new file mode 100644 index 000000000..58e5a41a0 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_arrow_collapse_black_alpha_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_check_white_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_check_white_24dp.xml new file mode 100644 index 000000000..17aca2af1 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_check_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_flag.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_delete_black_alpha_24dp.xml similarity index 88% rename from Example/nrf-mesh/app/src/main/res/drawable/ic_flag.xml rename to Example/nrf-mesh/app/src/main/res/drawable/ic_delete_black_alpha_24dp.xml index 6d918749f..2467ec501 100644 --- a/Example/nrf-mesh/app/src/main/res/drawable/ic_flag.xml +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_delete_black_alpha_24dp.xml @@ -24,9 +24,9 @@ android:width="24dp" android:height="24dp" android:alpha="0.60" - android:viewportHeight="24.0" - android:viewportWidth="24.0"> + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:fillColor="#FFFFFFFF" + android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/> diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_done_all_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_done_all_black_alpha_24dp.xml new file mode 100644 index 000000000..427787b46 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_done_all_black_alpha_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_done_all_nordic_lake_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_done_all_nordic_lake_24dp.xml new file mode 100644 index 000000000..93787d417 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_done_all_nordic_lake_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_done_all_white_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_done_all_white_24dp.xml new file mode 100644 index 000000000..3fa4edb58 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_done_all_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_earth.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_earth.xml deleted file mode 100644 index e61885354..000000000 --- a/Example/nrf-mesh/app/src/main/res/drawable/ic_earth.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_filter_list_black_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_filter_list_black_24dp.xml new file mode 100644 index 000000000..b99b672f4 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_filter_list_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_folder_provisioner_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_folder_provisioner_black_alpha_24dp.xml new file mode 100644 index 000000000..d0aa09e29 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_folder_provisioner_black_alpha_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_label_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_label_black_alpha_24dp.xml new file mode 100644 index 000000000..222f042ab --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_label_black_alpha_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_label_outline_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_label_outline_black_alpha_24dp.xml new file mode 100644 index 000000000..eab6e0479 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_label_outline_black_alpha_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_lock_open_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_lock_open_black_alpha_24dp.xml new file mode 100644 index 000000000..0c623121c --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_lock_open_black_alpha_24dp.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_range_black_alpha_48dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_black_alpha_48dp.xml new file mode 100644 index 000000000..29b0238b9 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_black_alpha_48dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_list.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_lake.xml similarity index 80% rename from Example/nrf-mesh/app/src/main/res/drawable/ic_list.xml rename to Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_lake.xml index 95bf6cf32..c57794627 100644 --- a/Example/nrf-mesh/app/src/main/res/drawable/ic_list.xml +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_lake.xml @@ -20,13 +20,12 @@ ~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - + + + + - - + android:height="24dp"/> + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_assignment.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_light_gray.xml similarity index 74% rename from Example/nrf-mesh/app/src/main/res/drawable/ic_assignment.xml rename to Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_light_gray.xml index 8866bc97f..4574420a8 100644 --- a/Example/nrf-mesh/app/src/main/res/drawable/ic_assignment.xml +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_light_gray.xml @@ -20,8 +20,12 @@ ~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - - - + + + + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_lock_open.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_medium_gray.xml similarity index 74% rename from Example/nrf-mesh/app/src/main/res/drawable/ic_lock_open.xml rename to Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_medium_gray.xml index 84bdf1e09..df3d82957 100644 --- a/Example/nrf-mesh/app/src/main/res/drawable/ic_lock_open.xml +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_medium_gray.xml @@ -20,8 +20,12 @@ ~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - - - + + + + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_red.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_red.xml new file mode 100644 index 000000000..7a42221a7 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_range_nordic_red.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_round_expand_more_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_round_expand_more_black_alpha_24dp.xml index ee5bc8c70..2ab479f42 100644 --- a/Example/nrf-mesh/app/src/main/res/drawable/ic_round_expand_more_black_alpha_24dp.xml +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_round_expand_more_black_alpha_24dp.xml @@ -20,8 +20,13 @@ ~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - - + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_scene_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_scene_black_alpha_24dp.xml new file mode 100644 index 000000000..7b3fd42f7 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_scene_black_alpha_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_shield_key_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_shield_key_black_alpha_24dp.xml new file mode 100644 index 000000000..12a045dcc --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_shield_key_black_alpha_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_shield_provisioner_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_shield_provisioner_black_alpha_24dp.xml new file mode 100644 index 000000000..4260bae83 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_shield_provisioner_black_alpha_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/Example/nrf-mesh/app/src/main/res/drawable/ic_vpn_key_black_alpha_24dp.xml b/Example/nrf-mesh/app/src/main/res/drawable/ic_vpn_key_black_alpha_24dp.xml index 6542836bb..dcbe3fcc7 100644 --- a/Example/nrf-mesh/app/src/main/res/drawable/ic_vpn_key_black_alpha_24dp.xml +++ b/Example/nrf-mesh/app/src/main/res/drawable/ic_vpn_key_black_alpha_24dp.xml @@ -1,32 +1,5 @@ - - - - + + diff --git a/Example/nrf-mesh/app/src/main/res/layout-land/activity_ranges.xml b/Example/nrf-mesh/app/src/main/res/layout-land/activity_ranges.xml new file mode 100644 index 000000000..0f35290ab --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/layout-land/activity_ranges.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/layout-land/info_range_legend.xml b/Example/nrf-mesh/app/src/main/res/layout-land/info_range_legend.xml new file mode 100644 index 000000000..ca20003ab --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/layout-land/info_range_legend.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/layout-sw600dp/fragment_network.xml b/Example/nrf-mesh/app/src/main/res/layout-sw600dp/fragment_network.xml index a81ec6995..ea5b33769 100644 --- a/Example/nrf-mesh/app/src/main/res/layout-sw600dp/fragment_network.xml +++ b/Example/nrf-mesh/app/src/main/res/layout-sw600dp/fragment_network.xml @@ -21,24 +21,22 @@ ~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - - - - + - - \ No newline at end of file + android:layout_margin="@dimen/activity_horizontal_margin" + android:text="@string/action_add_node" + app:icon="@drawable/ic_add_white"/> + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/layout-sw600dp/network_item.xml b/Example/nrf-mesh/app/src/main/res/layout-sw600dp/network_item.xml index 51492a4ab..26741795a 100644 --- a/Example/nrf-mesh/app/src/main/res/layout-sw600dp/network_item.xml +++ b/Example/nrf-mesh/app/src/main/res/layout-sw600dp/network_item.xml @@ -28,152 +28,156 @@ android:id="@+id/container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:foreground="?selectableItemBackgroundBorderless"> + android:background="@color/nordicRed"> - + + - - - - - - - - - + + + app:layout_constraintTop_toTopOf="@id/node_name" + app:srcCompat="@drawable/ic_mesh_48dp"/> - - - + app:layout_constraintStart_toEndOf="@id/icon" + app:layout_constraintTop_toTopOf="parent"/> - - - - - - - - - - - - + android:visibility="visible" + app:layout_constraintEnd_toEndOf="@id/node_name" + app:layout_constraintStart_toStartOf="@id/node_name" + app:layout_constraintTop_toBottomOf="@id/node_name"> + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/layout/activity_add_keys.xml b/Example/nrf-mesh/app/src/main/res/layout/activity_add_keys.xml new file mode 100644 index 000000000..a475a62c9 --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/layout/activity_add_keys.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/layout/activity_manage_app_keys.xml b/Example/nrf-mesh/app/src/main/res/layout/activity_app_keys.xml similarity index 50% rename from Example/nrf-mesh/app/src/main/res/layout/activity_manage_app_keys.xml rename to Example/nrf-mesh/app/src/main/res/layout/activity_app_keys.xml index 0333601e7..996eb19b9 100644 --- a/Example/nrf-mesh/app/src/main/res/layout/activity_manage_app_keys.xml +++ b/Example/nrf-mesh/app/src/main/res/layout/activity_app_keys.xml @@ -21,68 +21,84 @@ ~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - - - + android:theme="@style/AppTheme.AppBarOverlay" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - - + - + - - - + + + - + - + app:icon="@drawable/ic_add_white" + app:srcCompat="@drawable/ic_add_white"/> - \ No newline at end of file + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/layout/activity_config_groups.xml b/Example/nrf-mesh/app/src/main/res/layout/activity_config_groups.xml index 2a487dd21..fd790bb71 100644 --- a/Example/nrf-mesh/app/src/main/res/layout/activity_config_groups.xml +++ b/Example/nrf-mesh/app/src/main/res/layout/activity_config_groups.xml @@ -21,7 +21,7 @@ ~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - - - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/layout/activity_edit_key.xml b/Example/nrf-mesh/app/src/main/res/layout/activity_edit_key.xml new file mode 100644 index 000000000..a2608753d --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/layout/activity_edit_key.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/layout/activity_edit_provisioner.xml b/Example/nrf-mesh/app/src/main/res/layout/activity_edit_provisioner.xml new file mode 100644 index 000000000..0ef5a06ad --- /dev/null +++ b/Example/nrf-mesh/app/src/main/res/layout/activity_edit_provisioner.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Example/nrf-mesh/app/src/main/res/layout/activity_main.xml b/Example/nrf-mesh/app/src/main/res/layout/activity_main.xml index f0dcb970a..8df1e2dd1 100644 --- a/Example/nrf-mesh/app/src/main/res/layout/activity_main.xml +++ b/Example/nrf-mesh/app/src/main/res/layout/activity_main.xml @@ -21,7 +21,7 @@ ~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - - - - + + + - - - + diff --git a/Example/nrf-mesh/app/src/main/res/layout/activity_mesh_node_configuration.xml b/Example/nrf-mesh/app/src/main/res/layout/activity_mesh_node_configuration.xml deleted file mode 100644 index 4410dba6a..000000000 --- a/Example/nrf-mesh/app/src/main/res/layout/activity_mesh_node_configuration.xml +++ /dev/null @@ -1,539 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -