diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index e206d70d8..f75895965 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,5 +1,6 @@
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index e22580416..c75a99ee0 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -5,6 +5,7 @@
+
-
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 59436c989..fc2d64b25 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,8 +3,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 000000000..7f68460d8
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/app.iml b/app/app.iml
index 87f544ece..ea4ba425f 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -71,8 +71,9 @@
-
-
+
+
+
@@ -88,15 +89,18 @@
-
+
-
+
-
+
+
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 6c4ba7ebb..462efc494 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,14 +1,14 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 22
- buildToolsVersion '22.0.1'
+ compileSdkVersion 23
+ buildToolsVersion '23.0.0'
defaultConfig {
applicationId "no.nordicsemi.android.nrftoolbox"
minSdkVersion 18
- targetSdkVersion 22
- versionCode 36
- versionName "1.14.3"
+ targetSdkVersion 23
+ versionCode 37
+ versionName "1.15.0"
}
buildTypes {
release {
@@ -20,8 +20,13 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.android.support:appcompat-v7:22.2.1'
- compile 'com.android.support:design:22.2.1'
+ compile 'com.android.support:appcompat-v7:23.0.0'
+ compile 'com.android.support:design:23.0.0'
+ compile 'no.nordicsemi.android.support.v18:scanner:0.1.1'
+ compile('org.simpleframework:simple-xml:2.7.1') {
+ exclude group: 'stax', module: 'stax-api'
+ exclude group: 'xpp3', module: 'xpp3'
+ }
compile project(':dfu')
compile files('libs/achartengine-1.1.0.jar')
compile files('libs/nrf-logger-v2.0.jar')
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5805dcfba..9d4ca83d7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -22,17 +22,12 @@
-->
-
-
+ android:installLocation="auto">
+
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/PermissionRationaleFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/PermissionRationaleFragment.java
new file mode 100644
index 000000000..6b1c5c1ae
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/PermissionRationaleFragment.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015, 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.nrftoolbox;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
+
+public class PermissionRationaleFragment extends DialogFragment {
+ private static final String ARG_PERMISSION = "ARG_PERMISSION";
+ private static final String ARG_TEXT = "ARG_TEXT";
+
+ private PermissionDialogListener mListener;
+
+ public interface PermissionDialogListener {
+ public void onRequestPermission(final String permission);
+ }
+
+ @Override
+ public void onAttach(final Context context) {
+ super.onAttach(context);
+
+ if (context instanceof PermissionDialogListener) {
+ mListener = (PermissionDialogListener) context;
+ } else {
+ throw new IllegalArgumentException("The parent activity must impelemnt PermissionDialogListener");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+
+ public static PermissionRationaleFragment getInstance(final int aboutResId, final String permission) {
+ final PermissionRationaleFragment fragment = new PermissionRationaleFragment();
+
+ final Bundle args = new Bundle();
+ args.putInt(ARG_TEXT, aboutResId);
+ args.putString(ARG_PERMISSION, permission);
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ @NonNull
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final Bundle args = getArguments();
+ final StringBuilder text = new StringBuilder(getString(args.getInt(ARG_TEXT)));
+ return new AlertDialog.Builder(getActivity()).setTitle(R.string.permission_title).setMessage(text)
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ mListener.onRequestPermission(args.getString(ARG_PERMISSION));
+ }
+ }).create();
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java
index 58497033f..89017f353 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java
@@ -32,6 +32,7 @@
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.NotificationCompat;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
@@ -138,7 +139,7 @@ public void onWheelMeasurementReceived(final int wheelRevolutions, final int las
return;
if (mLastWheelRevolutions >= 0) {
- float timeDifference = 0;
+ float timeDifference;
if (lastWheelEventTime < mLastWheelEventTime)
timeDifference = (65535 + lastWheelEventTime - mLastWheelEventTime) / 1024.0f; // [s]
else
@@ -167,7 +168,7 @@ public void onCrankMeasurementReceived(int crankRevolutions, int lastCrankEventT
return;
if (mLastCrankRevolutions >= 0) {
- float timeDifference = 0;
+ float timeDifference;
if (lastCrankEventTime < mLastCrankEventTime)
timeDifference = (65535 + lastCrankEventTime - mLastCrankEventTime) / 1024.0f; // [s]
else
@@ -206,11 +207,12 @@ private void createNotification(final int messageResId, final int defaults) {
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT);
- final Notification.Builder builder = new Notification.Builder(this).setContentIntent(pendingIntent);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+ builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_csc);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
- builder.addAction(R.drawable.ic_action_bluetooth, getString(R.string.csc_notification_action_disconnect), disconnectAction);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.csc_notification_action_disconnect), disconnectAction));
final Notification notification = builder.build();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuActivity.java
index 04ac855e7..417601629 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuActivity.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuActivity.java
@@ -21,6 +21,7 @@
*/
package no.nordicsemi.android.nrftoolbox.dfu;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.LoaderManager.LoaderCallbacks;
@@ -38,10 +39,11 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Environment;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.DialogFragment;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
@@ -58,15 +60,13 @@
import android.widget.Toast;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
import no.nordicsemi.android.dfu.DfuProgressListener;
import no.nordicsemi.android.dfu.DfuProgressListenerAdapter;
-import no.nordicsemi.android.dfu.DfuServiceListenerHelper;
import no.nordicsemi.android.dfu.DfuServiceInitiator;
+import no.nordicsemi.android.dfu.DfuServiceListenerHelper;
import no.nordicsemi.android.nrftoolbox.AppHelpFragment;
+import no.nordicsemi.android.nrftoolbox.PermissionRationaleFragment;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.dfu.adapter.FileBrowserAppsAdapter;
import no.nordicsemi.android.nrftoolbox.dfu.fragment.UploadCancelFragment;
@@ -74,7 +74,7 @@
import no.nordicsemi.android.nrftoolbox.dfu.settings.SettingsActivity;
import no.nordicsemi.android.nrftoolbox.dfu.settings.SettingsFragment;
import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment;
-import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import no.nordicsemi.android.nrftoolbox.utility.FileHelper;
/**
* DfuActivity is the main DFU activity It implements DFUManagerCallbacks to receive callbacks from DFUManager class It implements
@@ -82,12 +82,9 @@
* landscape orientations
*/
public class DfuActivity extends AppCompatActivity implements LoaderCallbacks, ScannerFragment.OnDeviceSelectedListener,
- UploadCancelFragment.CancelFragmentListener {
+ UploadCancelFragment.CancelFragmentListener, PermissionRationaleFragment.PermissionDialogListener {
private static final String TAG = "DfuActivity";
- private static final String PREFS_SAMPLES_VERSION = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_SAMPLES_VERSION";
- private static final int CURRENT_SAMPLES_VERSION = 4;
-
private static final String PREFS_DEVICE_NAME = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_DEVICE_NAME";
private static final String PREFS_FILE_NAME = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_FILE_NAME";
private static final String PREFS_FILE_TYPE = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_FILE_TYPE";
@@ -104,6 +101,7 @@ public class DfuActivity extends AppCompatActivity implements LoaderCallbacks 1)
mTextUploading.setText(getString(R.string.dfu_status_uploading_part, currentPart, partsTotal));
else
@@ -228,7 +226,15 @@ protected void onCreate(final Bundle savedInstanceState) {
}
setGUI();
- ensureSamplesExist();
+ // Try to create sample files
+ if (FileHelper.newSamplesAvailable(this)) {
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+ FileHelper.createSamples(this);
+ } else {
+ final DialogFragment dialog = PermissionRationaleFragment.getInstance(R.string.permission_sd_text, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ dialog.show(getSupportFragmentManager(), null);
+ }
+ }
// restore saved state
mFileType = DfuService.TYPE_AUTO; // Default
@@ -303,6 +309,27 @@ protected void onPause() {
DfuServiceListenerHelper.unregisterProgressListener(this, mDfuProgressListener);
}
+ @Override
+ public void onRequestPermission(final String permission) {
+ ActivityCompat.requestPermissions(this, new String[] { permission }, PERMISSION_REQ);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(final int requestCode, final String[] permissions, final int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case PERMISSION_REQ: {
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // We have been granted the Manifest.permission.WRITE_EXTERNAL_STORAGE permission. Now we may proceed with exporting.
+ FileHelper.createSamples(this);
+ } else {
+ Toast.makeText(this, R.string.no_required_permission, Toast.LENGTH_SHORT).show();
+ }
+ break;
+ }
+ }
+ }
+
private void isBLESupported() {
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
showToast(R.string.no_ble);
@@ -322,168 +349,10 @@ private void showBLEDialog() {
}
private void showDeviceScanningDialog() {
- final ScannerFragment dialog = ScannerFragment.getInstance(DfuActivity.this, null, false); // Device that is advertising directly does not have the GENERAL_DISCOVERABLE nor LIMITED_DISCOVERABLE flag set.
+ final ScannerFragment dialog = ScannerFragment.getInstance(null); // Device that is advertising directly does not have the GENERAL_DISCOVERABLE nor LIMITED_DISCOVERABLE flag set.
dialog.show(getSupportFragmentManager(), "scan_fragment");
}
- private void ensureSamplesExist() {
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- final int version = preferences.getInt(PREFS_SAMPLES_VERSION, 0);
- if (version == CURRENT_SAMPLES_VERSION)
- return;
-
- /*
- * Copy example HEX files to the external storage. Files will be copied if the DFU Applications folder is missing
- */
- final File root = new File(Environment.getExternalStorageDirectory(), "Nordic Semiconductor");
- if (!root.exists()) {
- root.mkdir();
- }
- final File board = new File(root, "Board");
- if (!board.exists()) {
- board.mkdir();
- }
- final File nrf6310 = new File(board, "nrf6310");
- if (!nrf6310.exists()) {
- nrf6310.mkdir();
- }
- final File pca10028 = new File(board, "pca10028");
- if (!pca10028.exists()) {
- pca10028.mkdir();
- }
-
- // Remove old files. Those will be moved to a new folder structure
- new File(root, "ble_app_hrs_s110_v6_0_0.hex").delete();
- new File(root, "ble_app_rscs_s110_v6_0_0.hex").delete();
- new File(root, "ble_app_hrs_s110_v7_0_0.hex").delete();
- new File(root, "ble_app_rscs_s110_v7_0_0.hex").delete();
- new File(root, "blinky_arm_s110_v7_0_0.hex").delete();
- new File(root, "dfu_2_0.bat").delete(); // This file has been migrated to 3.0
- new File(root, "dfu_3_0.bat").delete(); // This file has been migrated to 3.1
- new File(root, "dfu_2_0.sh").delete(); // This file has been migrated to 3.0
- new File(root, "dfu_3_0.sh").delete(); // This file has been migrated to 3.1
- new File(root, "README.txt").delete(); // This file has been modified to match v.3.0+
-
- boolean oldCopied = false;
- boolean newCopied = false;
-
- // nrf6310 files
- File f = new File(nrf6310, "ble_app_hrs_s110_v6_0_0.hex");
- if (!f.exists()) {
- copyRawResource(R.raw.ble_app_hrs_s110_v6_0_0, f);
- oldCopied = true;
- }
- f = new File(nrf6310, "ble_app_rscs_s110_v6_0_0.hex");
- if (!f.exists()) {
- copyRawResource(R.raw.ble_app_rscs_s110_v6_0_0, f);
- oldCopied = true;
- }
- f = new File(nrf6310, "ble_app_hrs_s110_v7_0_0.hex");
- if (!f.exists()) {
- copyRawResource(R.raw.ble_app_hrs_s110_v7_0_0, f);
- oldCopied = true;
- }
- f = new File(nrf6310, "ble_app_rscs_s110_v7_0_0.hex");
- if (!f.exists()) {
- copyRawResource(R.raw.ble_app_rscs_s110_v7_0_0, f);
- oldCopied = true;
- }
- f = new File(nrf6310, "blinky_arm_s110_v7_0_0.hex");
- if (!f.exists()) {
- copyRawResource(R.raw.blinky_arm_s110_v7_0_0, f);
- oldCopied = true;
- }
- // PCA10028 files
- f = new File(pca10028, "blinky_s110_v7_1_0.hex");
- if (!f.exists()) {
- copyRawResource(R.raw.blinky_s110_v7_1_0, f);
- oldCopied = true;
- }
- f = new File(pca10028, "blinky_s110_v7_1_0_ext_init.dat");
- if (!f.exists()) {
- copyRawResource(R.raw.blinky_s110_v7_1_0_ext_init, f);
- oldCopied = true;
- }
- f = new File(pca10028, "ble_app_hrs_dfu_s110_v7_1_0.hex");
- if (!f.exists()) {
- copyRawResource(R.raw.ble_app_hrs_dfu_s110_v7_1_0, f);
- oldCopied = true;
- }
- f = new File(pca10028, "ble_app_hrs_dfu_s110_v7_1_0_ext_init.dat");
- if (!f.exists()) {
- copyRawResource(R.raw.ble_app_hrs_dfu_s110_v7_1_0_ext_init, f);
- oldCopied = true;
- }
- new File(root, "ble_app_hrs_dfu_s110_v8_0_0.zip").delete(); // name changed
- f = new File(pca10028, "ble_app_hrs_dfu_s110_v8_0_0_sdk_v8_0.zip");
- if (!f.exists()) {
- copyRawResource(R.raw.ble_app_hrs_dfu_s110_v8_0_0_sdk_v8_0, f);
- newCopied = true;
- }
- f = new File(pca10028, "ble_app_hrs_dfu_s110_v8_0_0_sdk_v9_0.zip");
- if (!f.exists()) {
- copyRawResource(R.raw.ble_app_hrs_dfu_s110_v8_0_0_sdk_v9_0, f);
- newCopied = true;
- }
- f = new File(pca10028, "ble_app_hrs_dfu_all_in_one_sdk_v9_0.zip");
- if (!f.exists()) {
- copyRawResource(R.raw.ble_app_hrs_dfu_all_in_one_sdk_v9_0, f);
- newCopied = true;
- }
-
- if (oldCopied)
- Toast.makeText(this, R.string.dfu_example_files_created, Toast.LENGTH_SHORT).show();
- else if (newCopied)
- Toast.makeText(this, R.string.dfu_example_new_files_created, Toast.LENGTH_SHORT).show();
-
- // Scripts
- newCopied = false;
- f = new File(root, "dfu_3_1.bat");
- if (!f.exists()) {
- copyRawResource(R.raw.dfu_win_3_1, f);
- newCopied = true;
- }
- f = new File(root, "dfu_3_1.sh");
- if (!f.exists()) {
- copyRawResource(R.raw.dfu_mac_3_1, f);
- newCopied = true;
- }
- f = new File(root, "README.txt");
- if (!f.exists()) {
- copyRawResource(R.raw.readme, f);
- }
- if (newCopied)
- Toast.makeText(this, R.string.dfu_scripts_created, Toast.LENGTH_SHORT).show();
-
- // Save the current version
- preferences.edit().putInt(PREFS_SAMPLES_VERSION, CURRENT_SAMPLES_VERSION).apply();
- }
-
- /**
- * Copies the file from res/raw with given id to given destination file. If dest does not exist it will be created.
- *
- * @param rawResId the resource id
- * @param dest destination file
- */
- private void copyRawResource(final int rawResId, final File dest) {
- try {
- final InputStream is = getResources().openRawResource(rawResId);
- final FileOutputStream fos = new FileOutputStream(dest);
-
- final byte[] buf = new byte[1024];
- int read;
- try {
- while ((read = is.read(buf)) > 0)
- fos.write(buf, 0, read);
- } finally {
- is.close();
- fos.close();
- }
- } catch (final IOException e) {
- DebugLogger.e(TAG, "Error while copying HEX file " + e.toString());
- }
- }
-
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.settings_and_about, menu);
@@ -841,7 +710,7 @@ public void onConnectClicked(final View view) {
public void onDeviceSelected(final BluetoothDevice device, final String name) {
mSelectedDevice = device;
mUploadButton.setEnabled(mStatusOk);
- mDeviceNameView.setText(name);
+ mDeviceNameView.setText(name != null ? name : getString(R.string.not_available));
}
@Override
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuInitiatorActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuInitiatorActivity.java
index 77c0fffef..81c4d0a38 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuInitiatorActivity.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuInitiatorActivity.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
+import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment;
/**
@@ -45,7 +46,7 @@ protected void onCreate(final Bundle savedInstanceState) {
finish();
if (savedInstanceState == null) {
- final ScannerFragment fragment = ScannerFragment.getInstance(this, null, false); // Device that is advertising directly does not have the GENERAL_DISCOVERABLE nor LIMITED_DISCOVERABLE flag set.
+ final ScannerFragment fragment = ScannerFragment.getInstance(null); // Device that is advertising directly does not have the GENERAL_DISCOVERABLE nor LIMITED_DISCOVERABLE flag set.
fragment.show(getSupportFragmentManager(), null);
}
}
@@ -57,7 +58,7 @@ public void onDeviceSelected(final BluetoothDevice device, final String name) {
final String path = intent.getStringExtra(DfuService.EXTRA_FILE_PATH);
final String initPath = intent.getStringExtra(DfuService.EXTRA_INIT_FILE_PATH);
final String address = device.getAddress();
- final String finalName = overwrittenName == null ? name : overwrittenName;
+ final String finalName = overwrittenName == null ? (name != null ? name : getString(R.string.not_available)) : overwrittenName;
final int type = intent.getIntExtra(DfuService.EXTRA_FILE_TYPE, DfuService.TYPE_AUTO);
final boolean keepBond = intent.getBooleanExtra(DfuService.EXTRA_KEEP_BOND, false);
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/AboutDfuPreference.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/AboutDfuPreference.java
index 24e860ed5..2c722ae4f 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/AboutDfuPreference.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/AboutDfuPreference.java
@@ -44,7 +44,7 @@ public AboutDfuPreference(Context context, AttributeSet attrs, int defStyle) {
@Override
protected void onClick() {
final Context context = getContext();
- final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://developer.nordicsemi.com/nRF51_SDK/doc/8.0.0/s110/html/a00078.html"));
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk52.v0.9.1%2Fexamples_ble_dfu.html&cp=4_0_0_4_2"));
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java
index 9ae3e1ecf..d30f3102a 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java
@@ -30,6 +30,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.NotificationCompat;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
@@ -135,11 +136,12 @@ private void createNotification(final int messageResId, final int defaults) {
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT);
- final Notification.Builder builder = new Notification.Builder(this).setContentIntent(pendingIntent);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+ builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_hts);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
- builder.addAction(R.drawable.ic_action_bluetooth, getString(R.string.hts_notification_action_disconnect), disconnectAction);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.hts_notification_action_disconnect), disconnectAction));
final Notification notification = builder.build();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileActivity.java
index c7b04289e..7e86746ea 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileActivity.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileActivity.java
@@ -188,7 +188,7 @@ public void onConnectClicked(final View view) {
if (isBLEEnabled()) {
if (!mDeviceConnected) {
setDefaultUI();
- showDeviceScanningDialog(getFilterUUID(), isDiscoverableRequired());
+ showDeviceScanningDialog(getFilterUUID());
} else {
mBleManager.disconnect();
}
@@ -225,8 +225,9 @@ public void onDeviceSelected(final BluetoothDevice device, final String name) {
mLogSession = LocalLogSession.newSession(getApplicationContext(), getLocalAuthorityLogger(), device.getAddress(), name);
}
}
+ mDeviceName = name;
mBleManager.setLogger(mLogSession);
- mDeviceNameView.setText(mDeviceName = name);
+ mDeviceNameView.setText(name != null ? name : getString(R.string.not_available));
mConnectButton.setText(R.string.action_disconnect);
mBleManager.connect(device);
}
@@ -389,30 +390,19 @@ protected String getDeviceName() {
*/
protected abstract UUID getFilterUUID();
- /**
- * Whether the scanner must search only for devices with GENERAL_DISCOVERABLE or LIMITER_DISCOVERABLE flag set.
- *
- * @return true if devices must have one of those flags set in their advertisement packets
- */
- protected boolean isDiscoverableRequired() {
- return true;
- }
-
/**
* Shows the scanner fragment.
*
* @param filter
* the UUID filter used to filter out available devices. The fragment will always show all bonded devices as there is no information about their
* services
- * @param discoverableRequired
- * true if devices must have GENERAL_DISCOVERABLE or LIMITED_DISCOVERABLE flags set in their advertisement packet
* @see #getFilterUUID()
*/
- private void showDeviceScanningDialog(final UUID filter, final boolean discoverableRequired) {
+ private void showDeviceScanningDialog(final UUID filter) {
runOnUiThread(new Runnable() {
@Override
public void run() {
- final ScannerFragment dialog = ScannerFragment.getInstance(BleProfileActivity.this, filter, discoverableRequired);
+ final ScannerFragment dialog = ScannerFragment.getInstance(filter);
dialog.show(getSupportFragmentManager(), "scan_fragment");
}
});
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileExpandableListActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileExpandableListActivity.java
index edddbeb2c..b543b9f98 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileExpandableListActivity.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileExpandableListActivity.java
@@ -185,7 +185,7 @@ public void onConnectClicked(final View view) {
if (isBLEEnabled()) {
if (!mDeviceConnected) {
setDefaultUI();
- showDeviceScanningDialog(getFilterUUID(), isDiscoverableRequired());
+ showDeviceScanningDialog(getFilterUUID());
} else {
mBleManager.disconnect();
}
@@ -222,8 +222,9 @@ public void onDeviceSelected(final BluetoothDevice device, final String name) {
mLogSession = LocalLogSession.newSession(getApplicationContext(), getLocalAuthorityLogger(), device.getAddress(), name);
}
}
+ mDeviceName = name;
mBleManager.setLogger(mLogSession);
- mDeviceNameView.setText(mDeviceName = name);
+ mDeviceNameView.setText(name != null ? name : getString(R.string.not_available));
mConnectButton.setText(R.string.action_disconnect);
mBleManager.connect(device);
}
@@ -396,28 +397,18 @@ protected String getDeviceName() {
*/
protected abstract UUID getFilterUUID();
- /**
- * Whether the scanner must search only for devices with GENERAL_DISCOVERABLE or LIMITER_DISCOVERABLE flag set.
- *
- * @return true if devices must have one of those flags set in their advertisement packets
- */
- protected boolean isDiscoverableRequired() {
- return true;
- }
-
/**
* Shows the scanner fragment.
*
* @param filter the UUID filter used to filter out available devices. The fragment will always show all bonded devices as there is no information about their
* services
- * @param discoverableRequired true if devices must have GENERAL_DISCOVERABLE or LIMITED_DISCOVERABLE flags set in their advertisement packet
* @see #getFilterUUID()
*/
- private void showDeviceScanningDialog(final UUID filter, final boolean discoverableRequired) {
+ private void showDeviceScanningDialog(final UUID filter) {
runOnUiThread(new Runnable() {
@Override
public void run() {
- final ScannerFragment dialog = ScannerFragment.getInstance(BleProfileExpandableListActivity.this, filter, discoverableRequired);
+ final ScannerFragment dialog = ScannerFragment.getInstance(filter);
dialog.show(getSupportFragmentManager(), "scan_fragment");
}
});
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileServiceReadyActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileServiceReadyActivity.java
index 3f0c16120..dd182783d 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileServiceReadyActivity.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileServiceReadyActivity.java
@@ -218,7 +218,8 @@ protected final void onCreate(final Bundle savedInstanceState) {
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_actionbar);
setSupportActionBar(toolbar);
-
+
+ setUpView();
onViewCreated(savedInstanceState);
LocalBroadcastManager.getInstance(this).registerReceiver(mCommonBroadcastReceiver, makeIntentFilter());
@@ -332,7 +333,14 @@ protected void onInitialize(final Bundle savedInstanceState) {
*
* @param savedInstanceState contains the data it most recently supplied in {@link #onSaveInstanceState(Bundle)}. Note: Otherwise it is null.
*/
- protected final void onViewCreated(final Bundle savedInstanceState) {
+ protected void onViewCreated(final Bundle savedInstanceState) {
+ // empty default implementation
+ }
+
+ /**
+ * Called after the view and the toolbar has been created.
+ */
+ protected final void setUpView() {
// set GUI
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mConnectButton = (Button) findViewById(R.id.action_connect);
@@ -395,7 +403,7 @@ public void onConnectClicked(final View view) {
if (isBLEEnabled()) {
if (mService == null) {
setDefaultUI();
- showDeviceScanningDialog(getFilterUUID(), isDiscoverableRequired());
+ showDeviceScanningDialog(getFilterUUID());
} else {
Logger.v(mLogSession, "Disconnecting...");
mService.disconnect();
@@ -433,7 +441,8 @@ public void onDeviceSelected(final BluetoothDevice device, final String name) {
mLogSession = LocalLogSession.newSession(getApplicationContext(), getLocalAuthorityLogger(), device.getAddress(), name);
}
}
- mDeviceNameView.setText(mDeviceName = name);
+ mDeviceName = name;
+ mDeviceNameView.setText(name != null ? name : getString(R.string.not_available));
mConnectButton.setText(R.string.action_disconnect);
// The device may not be in the range but the service will try to connect to it if it reach it
@@ -629,25 +638,15 @@ protected String getDeviceName() {
*/
protected abstract UUID getFilterUUID();
- /**
- * Whether the scanner must search only for devices with GENERAL_DISCOVERABLE or LIMITER_DISCOVERABLE flag set.
- *
- * @return true if devices must have one of those flags set in their advertisement packets
- */
- protected boolean isDiscoverableRequired() {
- return true;
- }
-
/**
* Shows the scanner fragment.
*
* @param filter the UUID filter used to filter out available devices. The fragment will always show all bonded devices as there is no information about their
* services
- * @param discoverableRequired true if devices must have GENERAL_DISCOVERABLE or LIMITED_DISCOVERABLE flags set in their advertisement packet
* @see #getFilterUUID()
*/
- private void showDeviceScanningDialog(final UUID filter, final boolean discoverableRequired) {
- final ScannerFragment dialog = ScannerFragment.getInstance(BleProfileServiceReadyActivity.this, filter, discoverableRequired);
+ private void showDeviceScanningDialog(final UUID filter) {
+ final ScannerFragment dialog = ScannerFragment.getInstance(filter);
dialog.show(getSupportFragmentManager(), "scan_fragment");
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java
index 84f53aba2..a7a7e921b 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java
@@ -32,6 +32,7 @@
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
+import android.support.v7.app.NotificationCompat;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
@@ -227,13 +228,14 @@ private void createNotification(final int messageResId, final int defaults) {
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT);
- final Notification.Builder builder = new Notification.Builder(this).setContentIntent(pendingIntent);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+ builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_proximity);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
- builder.addAction(R.drawable.ic_action_bluetooth, getString(R.string.proximity_notification_action_disconnect), disconnectAction);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.proximity_notification_action_disconnect), disconnectAction));
if (isConnected())
- builder.addAction(R.drawable.ic_stat_notify_proximity, getString(isImmediateAlertOn ? R.string.proximity_action_silentme : R.string.proximity_action_findme), secondAction);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_stat_notify_proximity, getString(isImmediateAlertOn ? R.string.proximity_action_silentme : R.string.proximity_action_findme), secondAction));
final Notification notification = builder.build();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java
index 47fc12efd..9e36a3ad9 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java
@@ -31,6 +31,7 @@
import android.content.IntentFilter;
import android.os.Handler;
import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.NotificationCompat;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
@@ -186,11 +187,12 @@ private void createNotification(final int messageResId, final int defaults) {
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT);
- final Notification.Builder builder = new Notification.Builder(this).setContentIntent(pendingIntent);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+ builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_rsc);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
- builder.addAction(R.drawable.ic_action_bluetooth, getString(R.string.rsc_notification_action_disconnect), disconnectAction);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.rsc_notification_action_disconnect), disconnectAction));
final Notification notification = builder.build();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/DeviceListAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/DeviceListAdapter.java
index c9079a34e..a38289c40 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/DeviceListAdapter.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/DeviceListAdapter.java
@@ -21,6 +21,7 @@
*/
package no.nordicsemi.android.nrftoolbox.scanner;
+import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,8 +31,11 @@
import android.widget.TextView;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.support.v18.scanner.ScanResult;
/**
* DeviceListAdapter class is list adapter for showing scanned Devices name, address and RSSI image based on RSSI values.
@@ -44,58 +48,50 @@ public class DeviceListAdapter extends BaseAdapter {
private final ArrayList mListBondedValues = new ArrayList<>();
private final ArrayList mListValues = new ArrayList<>();
private final Context mContext;
- private final ExtendedBluetoothDevice.AddressComparator comparator = new ExtendedBluetoothDevice.AddressComparator();
public DeviceListAdapter(Context context) {
mContext = context;
}
- public void addBondedDevice(ExtendedBluetoothDevice device) {
- mListBondedValues.add(device);
- notifyDataSetChanged();
- }
-
/**
- * Looks for the device with the same address as given one in the list of bonded devices. If the device has been found it updates its RSSI value.
- *
- * @param address
- * the device address
- * @param rssi
- * the RSSI of the scanned device
+ * Sets a list of bonded devices.
+ * @param devices list of bonded devices.
*/
- public void updateRssiOfBondedDevice(String address, int rssi) {
- comparator.address = address;
- final int indexInBonded = mListBondedValues.indexOf(comparator);
- if (indexInBonded >= 0) {
- ExtendedBluetoothDevice previousDevice = mListBondedValues.get(indexInBonded);
- previousDevice.rssi = rssi;
- notifyDataSetChanged();
+ public void addBondedDevices(final Set devices) {
+ final List bondedDevices = mListBondedValues;
+ for (BluetoothDevice device : devices) {
+ bondedDevices.add(new ExtendedBluetoothDevice(device));
}
+ notifyDataSetChanged();
}
/**
- * If such device exists on the bonded device list, this method does nothing. If not then the device is updated (rssi value) or added.
- *
- * @param device
- * the device to be added or updated
+ * Updates the list of not bonded devices.
+ * @param results list of results from the scanner
*/
- public void addOrUpdateDevice(ExtendedBluetoothDevice device) {
- final boolean indexInBonded = mListBondedValues.contains(device);
- if (indexInBonded) {
- return;
- }
-
- final int indexInNotBonded = mListValues.indexOf(device);
- if (indexInNotBonded >= 0) {
- ExtendedBluetoothDevice previousDevice = mListValues.get(indexInNotBonded);
- previousDevice.rssi = device.rssi;
- notifyDataSetChanged();
- return;
+ public void update(final List results) {
+ for (final ScanResult result : results) {
+ final ExtendedBluetoothDevice device = findDevice(result);
+ if (device == null) {
+ mListValues.add(new ExtendedBluetoothDevice(result));
+ } else {
+ device.name = result.getScanRecord() != null ? result.getScanRecord().getDeviceName() : null;
+ device.rssi = result.getRssi();
+ }
}
- mListValues.add(device);
notifyDataSetChanged();
}
+ private ExtendedBluetoothDevice findDevice(final ScanResult result) {
+ for (final ExtendedBluetoothDevice device : mListBondedValues)
+ if (device.matches(result))
+ return device;
+ for (final ExtendedBluetoothDevice device : mListValues)
+ if (device.matches(result))
+ return device;
+ return null;
+ }
+
public void clearDevices() {
mListValues.clear();
notifyDataSetChanged();
@@ -115,7 +111,7 @@ public Object getItem(int position) {
final int bondedCount = mListBondedValues.size() + 1; // 1 for the title
if (mListBondedValues.isEmpty()) {
if (position == 0)
- return R.string.scanner_subtitle__not_bonded;
+ return R.string.scanner_subtitle_not_bonded;
else
return mListValues.get(position - 1);
} else {
@@ -124,7 +120,7 @@ public Object getItem(int position) {
if (position < bondedCount)
return mListBondedValues.get(position - 1);
if (position == bondedCount)
- return R.string.scanner_subtitle__not_bonded;
+ return R.string.scanner_subtitle_not_bonded;
return mListValues.get(position - bondedCount - 1);
}
}
@@ -197,7 +193,7 @@ public View getView(int position, View oldView, ViewGroup parent) {
final String name = device.name;
holder.name.setText(name != null ? name : mContext.getString(R.string.not_available));
holder.address.setText(device.device.getAddress());
- if (!device.isBonded || device.rssi != ScannerFragment.NO_RSSI) {
+ if (!device.isBonded || device.rssi != ExtendedBluetoothDevice.NO_RSSI) {
final int rssiPercent = (int) (100.0f * (127.0f + device.rssi) / (127.0f + 20.0f));
holder.rssi.setImageLevel(rssiPercent);
holder.rssi.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ExtendedBluetoothDevice.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ExtendedBluetoothDevice.java
index 35899ab44..7a248b582 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ExtendedBluetoothDevice.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ExtendedBluetoothDevice.java
@@ -23,43 +23,31 @@
import android.bluetooth.BluetoothDevice;
+import no.nordicsemi.android.support.v18.scanner.ScanResult;
+
public class ExtendedBluetoothDevice {
+ /* package */ static final int NO_RSSI = -1000;
public final BluetoothDevice device;
/** The name is not parsed by some Android devices, f.e. Sony Xperia Z1 with Android 4.3 (C6903). It needs to be parsed manually. */
public String name;
public int rssi;
public boolean isBonded;
- public ExtendedBluetoothDevice(BluetoothDevice device, String name, int rssi, boolean isBonded) {
- this.device = device;
- this.name = name;
- this.rssi = rssi;
- this.isBonded = isBonded;
+ public ExtendedBluetoothDevice(final ScanResult scanResult) {
+ this.device = scanResult.getDevice();
+ this.name = scanResult.getScanRecord() != null ? scanResult.getScanRecord().getDeviceName() : null;
+ this.rssi = scanResult.getRssi();
+ this.isBonded = false;
}
- @Override
- public boolean equals(Object o) {
- if (o instanceof ExtendedBluetoothDevice) {
- final ExtendedBluetoothDevice that = (ExtendedBluetoothDevice) o;
- return device.getAddress().equals(that.device.getAddress());
- }
- return super.equals(o);
+ public ExtendedBluetoothDevice(final BluetoothDevice device) {
+ this.device = device;
+ this.name = device.getName();
+ this.rssi = NO_RSSI;
+ this.isBonded = true;
}
- /**
- * Class used as a temporary comparator to find the device in the List of {@link ExtendedBluetoothDevice}s. This must be done this way, because List#indexOf and List#contains use the parameter's
- * equals method, not the object's from list. See {@link DeviceListAdapter#updateRssiOfBondedDevice(String, int)} for example
- */
- public static class AddressComparator {
- public String address;
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof ExtendedBluetoothDevice) {
- final ExtendedBluetoothDevice that = (ExtendedBluetoothDevice) o;
- return address.equals(that.device.getAddress());
- }
- return super.equals(o);
- }
+ public boolean matches(final ScanResult scanResult) {
+ return device.getAddress().equals(scanResult.getDevice().getAddress());
}
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerFragment.java
index 2b758fab0..c5e5366d2 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerFragment.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerFragment.java
@@ -21,6 +21,7 @@
*/
package no.nordicsemi.android.nrftoolbox.scanner;
+import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.bluetooth.BluetoothAdapter;
@@ -28,61 +29,65 @@
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelUuid;
import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
import android.support.v4.app.DialogFragment;
+import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
+import android.widget.Toast;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.R;
-import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+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.ScanResult;
+import no.nordicsemi.android.support.v18.scanner.ScanSettings;
/**
- * ScannerFragment class scan required BLE devices and shows them in a list. This class scans and filter devices with standard BLE Service UUID and devices with custom BLE Service UUID It contains a
- * list and a button to scan/cancel. There is a interface {@link OnDeviceSelectedListener} which is implemented by activity in order to receive selected device. The scanning will continue for 5
- * seconds and then stop
+ * ScannerFragment class scan required BLE devices and shows them in a list. This class scans and filter devices with standard BLE Service UUID and devices with custom BLE Service UUID. It contains a
+ * list and a button to scan/cancel. There is a interface {@link OnDeviceSelectedListener} which is implemented by activity in order to receive selected device. The scanning will continue to scan
+ * for 5 seconds and then stop.
*/
public class ScannerFragment extends DialogFragment {
private final static String TAG = "ScannerFragment";
private final static String PARAM_UUID = "param_uuid";
- private final static String DISCOVERABLE_REQUIRED = "discoverable_required";
private final static long SCAN_DURATION = 5000;
+ private final static int REQUEST_PERMISSION_REQ_CODE = 34; // any 8-bit number
+
private BluetoothAdapter mBluetoothAdapter;
private OnDeviceSelectedListener mListener;
private DeviceListAdapter mAdapter;
private final Handler mHandler = new Handler();
private Button mScanButton;
- private boolean mDiscoverableRequired;
- private UUID mUuid;
+ private View mPermissionRationale;
- private boolean mIsScanning = false;
+ private ParcelUuid mUuid;
- private static final boolean DEVICE_IS_BONDED = true;
- private static final boolean DEVICE_NOT_BONDED = false;
- /* package */static final int NO_RSSI = -1000;
+ private boolean mIsScanning = false;
- /**
- * Static implementation of fragment so that it keeps data when phone orientation is changed For standard BLE Service UUID, we can filter devices using normal android provided command
- * startScanLe() with required BLE Service UUID For custom BLE Service UUID, we will use class ScannerServiceParser to filter out required device.
- */
- public static ScannerFragment getInstance(final Context context, final UUID uuid, final boolean discoverableRequired) {
+ public static ScannerFragment getInstance(final UUID uuid) {
final ScannerFragment fragment = new ScannerFragment();
final Bundle args = new Bundle();
- args.putParcelable(PARAM_UUID, new ParcelUuid(uuid));
- args.putBoolean(DISCOVERABLE_REQUIRED, discoverableRequired);
+ if (uuid != null)
+ args.putParcelable(PARAM_UUID, new ParcelUuid(uuid));
fragment.setArguments(args);
return fragment;
}
@@ -127,10 +132,8 @@ public void onCreate(final Bundle savedInstanceState) {
final Bundle args = getArguments();
if (args.containsKey(PARAM_UUID)) {
- final ParcelUuid pu = args.getParcelable(PARAM_UUID);
- mUuid = pu.getUuid();
+ mUuid = args.getParcelable(PARAM_UUID);
}
- mDiscoverableRequired = args.getBoolean(DISCOVERABLE_REQUIRED);
final BluetoothManager manager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = manager.getAdapter();
@@ -142,9 +145,6 @@ public void onDestroyView() {
super.onDestroyView();
}
- /**
- * When dialog is created then set AlertDialog with list and button views.
- */
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
@@ -167,6 +167,8 @@ public void onItemClick(final AdapterView> parent, final View view, final int
}
});
+ mPermissionRationale = dialogView.findViewById(R.id.permission_rationale); // this is not null only on API23+
+
mScanButton = (Button) dialogView.findViewById(R.id.action_cancel);
mScanButton.setOnClickListener(new View.OnClickListener() {
@Override
@@ -194,16 +196,54 @@ public void onCancel(DialogInterface dialog) {
mListener.onDialogCanceled();
}
+ @Override
+ public void onRequestPermissionsResult(final int requestCode, final @NonNull String[] permissions, final @NonNull int[] grantResults) {
+ switch (requestCode) {
+ case REQUEST_PERMISSION_REQ_CODE: {
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // We have been granted the Manifest.permission.ACCESS_COARSE_LOCATION permission. Now we may proceed with scanning.
+ startScan();
+ } else {
+ mPermissionRationale.setVisibility(View.VISIBLE);
+ Toast.makeText(getActivity(), R.string.no_required_permission, Toast.LENGTH_SHORT).show();
+ }
+ break;
+ }
+ }
+ }
+
/**
* Scan for 5 seconds and then stop scanning when a BluetoothLE device is found then mLEScanCallback is activated This will perform regular scan for custom BLE Service UUID and then filter out.
* using class ScannerServiceParser
*/
private void startScan() {
+ // Since Android 6.0 we need to obtain either Manifest.permission.ACCESS_COARSE_LOCATION or Manifest.permission.ACCESS_FINE_LOCATION to be able to scan for
+ // Bluetooth LE devices. This is related to beacons as proximity devices.
+ // On API older than Marshmallow the following code does nothing.
+ if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ // When user pressed Deny and still wants to use this functionality, show the rationale
+ if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.ACCESS_COARSE_LOCATION) && mPermissionRationale.getVisibility() == View.GONE) {
+ mPermissionRationale.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_PERMISSION_REQ_CODE);
+ return;
+ }
+
+ // Hide the rationale message, we don't need it anymore.
+ if (mPermissionRationale != null)
+ mPermissionRationale.setVisibility(View.GONE);
+
mAdapter.clearDevices();
mScanButton.setText(R.string.scanner_action_cancel);
- // Samsung Note II with Android 4.3 build JSS15J.N7100XXUEMK9 is not filtering by UUID at all. We must parse UUIDs manually
- mBluetoothAdapter.startLeScan(mLEScanCallback);
+ final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
+ final ScanSettings settings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).setReportDelay(1000).setUseHardwareBatchingIfSupported(false).build();
+ final List filters = new ArrayList<>();
+ filters.add(new ScanFilter.Builder().setServiceUuid(mUuid).build());
+ scanner.startScan(filters, settings, scanCallback);
mIsScanning = true;
mHandler.postDelayed(new Runnable() {
@@ -217,68 +257,38 @@ public void run() {
}
/**
- * Stop scan if user tap Cancel button.
+ * Stop scan if user tap Cancel button
*/
private void stopScan() {
if (mIsScanning) {
mScanButton.setText(R.string.scanner_action_scan);
- mBluetoothAdapter.stopLeScan(mLEScanCallback);
+
+ final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
+ scanner.stopScan(scanCallback);
+
mIsScanning = false;
}
}
- private void addBondedDevices() {
- final Set devices = mBluetoothAdapter.getBondedDevices();
- for (BluetoothDevice device : devices) {
- mAdapter.addBondedDevice(new ExtendedBluetoothDevice(device, device.getName(), NO_RSSI, DEVICE_IS_BONDED));
+ private ScanCallback scanCallback = new ScanCallback() {
+ @Override
+ public void onScanResult(final int callbackType, final ScanResult result) {
+ // do nothing
}
- }
- /**
- * if scanned device already in the list then update it otherwise add as a new device
- */
- private void addScannedDevice(final BluetoothDevice device, final String name, final int rssi, final boolean isBonded) {
- if (getActivity() != null)
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mAdapter.addOrUpdateDevice(new ExtendedBluetoothDevice(device, name, rssi, isBonded));
- }
- });
- }
-
- /**
- * if scanned device already in the list then update it otherwise add as a new device.
- */
- private void updateScannedDevice(final BluetoothDevice device, final int rssi) {
- if (getActivity() != null)
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mAdapter.updateRssiOfBondedDevice(device.getAddress(), rssi);
- }
- });
- }
+ @Override
+ public void onBatchScanResults(final List results) {
+ mAdapter.update(results);
+ }
- /**
- * Callback for scanned devices class {@link ScannerServiceParser} will be used to filter devices with custom BLE service UUID then the device will be added in a list.
- */
- private final BluetoothAdapter.LeScanCallback mLEScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
- public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
- if (device != null) {
- updateScannedDevice(device, rssi);
- try {
- if (ScannerServiceParser.decodeDeviceAdvData(scanRecord, mUuid, mDiscoverableRequired)) {
- // On some devices device.getName() is always null. We have to parse the name manually :(
- // This bug has been found on Sony Xperia Z1 (C6903) with Android 4.3.
- // https://devzone.nordicsemi.com/index.php/cannot-see-device-name-in-sony-z1
- addScannedDevice(device, ScannerServiceParser.decodeDeviceName(scanRecord), rssi, DEVICE_NOT_BONDED);
- }
- } catch (Exception e) {
- DebugLogger.e(TAG, "Invalid data in Advertisement packet " + e.toString());
- }
- }
+ public void onScanFailed(final int errorCode) {
+ // should never be called
}
};
+
+ private void addBondedDevices() {
+ final Set devices = mBluetoothAdapter.getBondedDevices();
+ mAdapter.addBondedDevices(devices);
+ }
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerServiceParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerServiceParser.java
deleted file mode 100644
index 6d903f481..000000000
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerServiceParser.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (c) 2015, 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.nrftoolbox.scanner;
-
-import android.bluetooth.BluetoothDevice;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.util.UUID;
-
-/**
- * ScannerServiceParser is responsible to parse scanning data and it check if scanned device has required service in it.
- */
-public class ScannerServiceParser {
- private static final String TAG = "ScannerServiceParser";
-
- private static final int FLAGS_BIT = 0x01;
- private static final int SERVICES_MORE_AVAILABLE_16_BIT = 0x02;
- private static final int SERVICES_COMPLETE_LIST_16_BIT = 0x03;
- private static final int SERVICES_MORE_AVAILABLE_32_BIT = 0x04;
- private static final int SERVICES_COMPLETE_LIST_32_BIT = 0x05;
- private static final int SERVICES_MORE_AVAILABLE_128_BIT = 0x06;
- private static final int SERVICES_COMPLETE_LIST_128_BIT = 0x07;
- private static final int SHORTENED_LOCAL_NAME = 0x08;
- private static final int COMPLETE_LOCAL_NAME = 0x09;
-
- private static final byte LE_LIMITED_DISCOVERABLE_MODE = 0x01;
- private static final byte LE_GENERAL_DISCOVERABLE_MODE = 0x02;
-
- /**
- * Checks if device is connectible (as Android cannot get this information directly we just check if it has GENERAL DISCOVERABLE or LIMITED DISCOVERABLE flag set) and has required service UUID in
- * the advertising packet. The service UUID may be null.
- *
- * For further details on parsing BLE advertisement packet data see https://developer.bluetooth.org/Pages/default.aspx Bluetooth Core Specifications Volume 3, Part C, and Section 8
- *
- */
- public static boolean decodeDeviceAdvData(byte[] data, UUID requiredUUID, boolean discoverableRequired) {
- final String uuid = requiredUUID != null ? requiredUUID.toString() : null;
- if (data != null) {
- boolean connectible = !discoverableRequired;
- boolean valid = uuid == null;
- if (connectible && valid)
- return true;
- int fieldLength, fieldName;
- int packetLength = data.length;
- for (int index = 0; index < packetLength; index++) {
- fieldLength = data[index];
- if (fieldLength == 0) {
- return connectible && valid;
- }
- fieldName = data[++index];
-
- if (uuid != null) {
- if (fieldName == SERVICES_MORE_AVAILABLE_16_BIT || fieldName == SERVICES_COMPLETE_LIST_16_BIT) {
- for (int i = index + 1; i < index + fieldLength - 1; i += 2)
- valid = valid || decodeService16BitUUID(uuid, data, i, 2);
- } else if (fieldName == SERVICES_MORE_AVAILABLE_32_BIT || fieldName == SERVICES_COMPLETE_LIST_32_BIT) {
- for (int i = index + 1; i < index + fieldLength - 1; i += 4)
- valid = valid || decodeService32BitUUID(uuid, data, i, 4);
- } else if (fieldName == SERVICES_MORE_AVAILABLE_128_BIT || fieldName == SERVICES_COMPLETE_LIST_128_BIT) {
- for (int i = index + 1; i < index + fieldLength - 1; i += 16)
- valid = valid || decodeService128BitUUID(uuid, data, i, 16);
- }
- }
- if (!connectible && fieldName == FLAGS_BIT) {
- int flags = data[index + 1];
- connectible = (flags & (LE_GENERAL_DISCOVERABLE_MODE | LE_LIMITED_DISCOVERABLE_MODE)) > 0;
- }
- index += fieldLength - 1;
- }
- return connectible && valid;
- }
- return false;
- }
-
- /**
- * Decodes the device name from Complete Local Name or Shortened Local Name field in Advertisement packet. Usually if should be done by {@link BluetoothDevice#getName()} method but some phones
- * skips that, f.e. Sony Xperia Z1 (C6903) with Android 4.3 where getName() always returns null. In order to show the device name correctly we have to parse it manually :(
- */
- public static String decodeDeviceName(byte[] data) {
- String name = null;
- int fieldLength, fieldName;
- int packetLength = data.length;
- for (int index = 0; index < packetLength; index++) {
- fieldLength = data[index];
- if (fieldLength == 0)
- break;
- fieldName = data[++index];
-
- if (fieldName == COMPLETE_LOCAL_NAME || fieldName == SHORTENED_LOCAL_NAME) {
- name = decodeLocalName(data, index + 1, fieldLength - 1);
- break;
- }
- index += fieldLength - 1;
- }
- return name;
- }
-
- /**
- * Decodes the local name
- */
- public static String decodeLocalName(final byte[] data, final int start, final int length) {
- try {
- return new String(data, start, length, "UTF-8");
- } catch (final UnsupportedEncodingException e) {
- Log.e(TAG, "Unable to convert the complete local name to UTF-8", e);
- return null;
- } catch (final IndexOutOfBoundsException e) {
- Log.e(TAG, "Error when reading complete local name", e);
- return null;
- }
- }
-
- /**
- * check for required Service UUID inside device
- */
- private static boolean decodeService16BitUUID(String uuid, byte[] data, int startPosition, int serviceDataLength) {
- String serviceUUID = Integer.toHexString(decodeUuid16(data, startPosition));
- String requiredUUID = uuid.substring(4, 8);
-
- return serviceUUID.equals(requiredUUID);
- }
-
- /**
- * check for required Service UUID inside device
- */
- private static boolean decodeService32BitUUID(String uuid, byte[] data, int startPosition, int serviceDataLength) {
- String serviceUUID = Integer.toHexString(decodeUuid16(data, startPosition + serviceDataLength - 4));
- String requiredUUID = uuid.substring(4, 8);
-
- return serviceUUID.equals(requiredUUID);
- }
-
- /**
- * check for required Service UUID inside device
- */
- private static boolean decodeService128BitUUID(String uuid, byte[] data, int startPosition, int serviceDataLength) {
- String serviceUUID = Integer.toHexString(decodeUuid16(data, startPosition + serviceDataLength - 4));
- String requiredUUID = uuid.substring(4, 8);
-
- return serviceUUID.equals(requiredUUID);
- }
-
- private static int decodeUuid16(final byte[] data, final int start) {
- final int b1 = data[start] & 0xff;
- final int b2 = data[start + 1] & 0xff;
-
- return (b2 << 8 | b1);
- }
-}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/template/TemplateService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/template/TemplateService.java
index 6b0ba5d4a..8150586bb 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/template/TemplateService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/template/TemplateService.java
@@ -30,6 +30,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.NotificationCompat;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
@@ -141,11 +142,12 @@ private void createNotification(final int messageResId, final int defaults) {
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT);
- final Notification.Builder builder = new Notification.Builder(this).setContentIntent(pendingIntent);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+ builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_template);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
- builder.addAction(R.drawable.ic_action_bluetooth, getString(R.string.template_notification_action_disconnect), disconnectAction);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.template_notification_action_disconnect), disconnectAction));
final Notification notification = builder.build();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java
index 800b909b1..db731f4e1 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java
@@ -22,38 +22,131 @@
package no.nordicsemi.android.nrftoolbox.uart;
+import android.Manifest;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.TransitionDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.DialogFragment;
import android.support.v4.widget.SlidingPaneLayout;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.NotificationCompat;
+import android.util.Log;
+import android.view.Menu;
import android.view.View;
-
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import org.simpleframework.xml.Serializer;
+import org.simpleframework.xml.core.Persister;
+import org.simpleframework.xml.strategy.Strategy;
+import org.simpleframework.xml.strategy.Type;
+import org.simpleframework.xml.strategy.Visitor;
+import org.simpleframework.xml.strategy.VisitorStrategy;
+import org.simpleframework.xml.stream.Format;
+import org.simpleframework.xml.stream.HyphenStyle;
+import org.simpleframework.xml.stream.InputNode;
+import org.simpleframework.xml.stream.NodeMap;
+import org.simpleframework.xml.stream.OutputNode;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.dfu.adapter.FileBrowserAppsAdapter;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
-
-public class UARTActivity extends BleProfileServiceReadyActivity implements UARTControlFragment.ControlFragmentListener, UARTInterface {
+import no.nordicsemi.android.nrftoolbox.uart.database.DatabaseHelper;
+import no.nordicsemi.android.nrftoolbox.uart.domain.Command;
+import no.nordicsemi.android.nrftoolbox.uart.domain.UartConfiguration;
+import no.nordicsemi.android.nrftoolbox.utility.FileHelper;
+import no.nordicsemi.android.nrftoolbox.widget.ClosableSpinner;
+
+public class UARTActivity extends BleProfileServiceReadyActivity implements UARTInterface,
+ UARTNewConfigurationDialogFragment.NewConfigurationDialogListener, UARTConfigurationsAdapter.ActionListener, AdapterView.OnItemSelectedListener {
+ private final static String TAG = "UARTActivity";
+
+ private final static String PREFS_BUTTON_ENABLED = "prefs_uart_enabled_";
+ private final static String PREFS_BUTTON_COMMAND = "prefs_uart_command_";
+ private final static String PREFS_BUTTON_ICON = "prefs_uart_icon_";
+ private final static String PREFS_CONFIGURATION = "configuration_id";
private final static String SIS_EDIT_MODE = "sis_edit_mode";
+ private final static int SELECT_FILE_REQ = 2678; // random
+ private final static int PERMISSION_REQ = 24; // random, 8-bit
+
+ /** The current configuration. */
+ private UartConfiguration mConfiguration;
+ private DatabaseHelper mDatabaseHelper;
+ private SharedPreferences mPreferences;
+ private UARTConfigurationsAdapter mConfigurationsAdapter;
+ private ClosableSpinner mConfigurationSpinner;
private SlidingPaneLayout mSlider;
private UARTService.UARTBinder mServiceBinder;
+ private ConfigurationListener mConfigurationListener;
private boolean mEditMode;
+ public interface ConfigurationListener {
+ public void onConfigurationModified();
+ public void onConfigurationChanged(final UartConfiguration configuration);
+ public void setEditMode(final boolean editMode);
+ }
+
+ public void setConfigurationListener(final ConfigurationListener listener) {
+ mConfigurationListener = listener;
+ }
+
@Override
protected Class extends BleProfileService> getServiceClass() {
return UARTService.class;
}
+ @Override
+ protected int getLoggerProfileTitle() {
+ return R.string.uart_feature_title;
+ }
+
+ @Override
+ protected Uri getLocalAuthorityLogger() {
+ return UARTLocalLogContentProvider.AUTHORITY_URI;
+ }
+
+ @Override
+ protected void setDefaultUI() {
+ // empty
+ }
+
@Override
protected void onServiceBinded(final UARTService.UARTBinder binder) {
mServiceBinder = binder;
@@ -64,6 +157,14 @@ protected void onServiceUnbinded() {
mServiceBinder = null;
}
+ @Override
+ protected void onInitialize(final Bundle savedInstanceState) {
+ mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ mDatabaseHelper = new DatabaseHelper(this);
+ ensureFirstConfiguration(mDatabaseHelper);
+ mConfigurationsAdapter = new UARTConfigurationsAdapter(this, this, mDatabaseHelper.getServerConfigurationsNames());
+ }
+
@Override
protected void onCreateView(final Bundle savedInstanceState) {
setContentView(R.layout.activity_feature_uart);
@@ -84,6 +185,16 @@ public void onPanelClosed(final View panel) {
}
}
+ @Override
+ protected void onViewCreated(final Bundle savedInstanceState) {
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+
+ final ClosableSpinner configurationSpinner = mConfigurationSpinner = (ClosableSpinner) findViewById(R.id.toolbar_spinner);
+ configurationSpinner.setOnItemSelectedListener(this);
+ configurationSpinner.setAdapter(mConfigurationsAdapter);
+ configurationSpinner.setSelection(mConfigurationsAdapter.getItemPosition(mPreferences.getLong(PREFS_CONFIGURATION, 0)));
+ }
+
@Override
protected void onRestoreInstanceState(final @NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
@@ -99,31 +210,6 @@ public void onSaveInstanceState(final Bundle outState) {
outState.putBoolean(SIS_EDIT_MODE, mEditMode);
}
- @Override
- protected boolean onOptionsItemSelected(int itemId) {
- switch (itemId) {
- case R.id.action_show_log:
- mSlider.openPane();
- return true;
- }
- return false;
- }
-
- @Override
- protected int getLoggerProfileTitle() {
- return R.string.uart_feature_title;
- }
-
- @Override
- protected Uri getLocalAuthorityLogger() {
- return UARTLocalLogContentProvider.AUTHORITY_URI;
- }
-
- @Override
- protected void setDefaultUI() {
- // empty
- }
-
@Override
public void onServicesDiscovered(final boolean optionalServicesFound) {
// do nothing
@@ -154,20 +240,15 @@ protected UUID getFilterUUID() {
return null; // not used
}
- @Override
- protected boolean isDiscoverableRequired() {
- return false;
- }
-
@Override
public void send(final String text) {
if (mServiceBinder != null)
mServiceBinder.send(text);
}
- @Override
public void setEditMode(final boolean editMode) {
setEditMode(editMode, true);
+ invalidateOptionsMenu();
}
@Override
@@ -184,6 +265,305 @@ public void onBackPressed() {
super.onBackPressed();
}
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.uart_menu_configurations, menu);
+ getMenuInflater().inflate(mEditMode ? R.menu.uart_menu_config : R.menu.uart_menu, menu);
+
+ final int configurationsCount = mDatabaseHelper.getConfigurationsCount();
+ menu.findItem(R.id.action_remove).setVisible(configurationsCount > 1);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ protected boolean onOptionsItemSelected(int itemId) {
+ final String name = mConfiguration.getName();
+ switch (itemId) {
+ case R.id.action_configure:
+ setEditMode(!mEditMode);
+ return true;
+ case R.id.action_show_log:
+ mSlider.openPane();
+ return true;
+ case R.id.action_share: {
+ final String xml = mDatabaseHelper.getConfiguration(mConfigurationSpinner.getSelectedItemId());
+
+ final Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setType("text/xml");
+ intent.putExtra(Intent.EXTRA_TEXT, xml);
+ intent.putExtra(Intent.EXTRA_SUBJECT, mConfiguration.getName());
+ try {
+ startActivity(intent);
+ } catch (final ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.no_uri_application, Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ }
+ case R.id.action_export: {
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+ exportConfiguration();
+ } else {
+ ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, PERMISSION_REQ);
+ }
+ return true;
+ }
+ case R.id.action_rename: {
+ final DialogFragment fragment = UARTNewConfigurationDialogFragment.getInstance(name, false);
+ fragment.show(getSupportFragmentManager(), null);
+ // onNewConfiguration(name, false) will be called when user press OK
+ return true;
+ }
+ case R.id.action_duplicate: {
+ final DialogFragment fragment = UARTNewConfigurationDialogFragment.getInstance(name, true);
+ fragment.show(getSupportFragmentManager(), null);
+ // onNewConfiguration(name, true) will be called when user press OK
+ return true;
+ }
+ case R.id.action_remove: {
+ mDatabaseHelper.removeDeletedServerConfigurations(); // just to be sure nothing has left
+ mDatabaseHelper.deleteConfiguration(name);
+ refreshConfigurations();
+
+ final Snackbar snackbar = Snackbar.make(mSlider, R.string.uart_configuration_deleted, Snackbar.LENGTH_INDEFINITE).setAction(R.string.uart_action_undo, new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mDatabaseHelper.restoreDeletedServerConfigurations();
+ refreshConfigurations();
+ }
+ });
+ snackbar.setDuration(5000); // This is not an error
+ snackbar.show();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onRequestPermissionsResult(final int requestCode, final @NonNull String[] permissions, final @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case PERMISSION_REQ: {
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // We have been granted the Manifest.permission.WRITE_EXTERNAL_STORAGE permission. Now we may proceed with exporting.
+ exportConfiguration();
+ } else {
+ Toast.makeText(this, R.string.no_required_permission, Toast.LENGTH_SHORT).show();
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onItemSelected(final AdapterView> parent, final View view, final int position, final long id) {
+ if (position > 0) { // FIXME this is called twice after rotation.
+ try {
+ final String xml = mDatabaseHelper.getConfiguration(id);
+ final Format format = new Format(new HyphenStyle());
+ final Serializer serializer = new Persister(format);
+ mConfiguration = serializer.read(UartConfiguration.class, xml);
+ mConfigurationListener.onConfigurationChanged(mConfiguration);
+ } catch (final Exception e) {
+ Log.e(TAG, "Selecting configuration failed", e);
+
+ String message;
+ if (e.getLocalizedMessage() != null)
+ message = e.getLocalizedMessage();
+ else if (e.getCause() != null && e.getCause().getLocalizedMessage() != null)
+ message = e.getCause().getLocalizedMessage();
+ else
+ message = "Unknown error";
+ final String msg = message;
+ Snackbar.make(mSlider, R.string.uart_configuration_loading_failed, Snackbar.LENGTH_INDEFINITE).setAction(R.string.uart_action_details, new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ new AlertDialog.Builder(UARTActivity.this).setMessage(msg).setTitle(R.string.uart_action_details).setPositiveButton(R.string.ok, null).show();
+ }
+ }).show();
+ return;
+ }
+
+ mPreferences.edit().putLong(PREFS_CONFIGURATION, id).apply();
+ }
+ }
+
+ @Override
+ public void onNothingSelected(final AdapterView> parent) {
+ // do nothing
+ }
+
+ @Override
+ public void onNewConfigurationClick() {
+ // No item has been selected. We must close the spinner manually.
+ mConfigurationSpinner.close();
+
+ // Open the dialog
+ final DialogFragment fragment = UARTNewConfigurationDialogFragment.getInstance(null, false);
+ fragment.show(getSupportFragmentManager(), null);
+
+ // onNewConfiguration(null, false) will be called when user press OK
+ }
+
+ @Override
+ public void onImportClick() {
+ // No item has been selected. We must close the spinner manually.
+ mConfigurationSpinner.close();
+
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("text/xml");
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ // file browser has been found on the device
+ startActivityForResult(intent, SELECT_FILE_REQ);
+ } else {
+ // there is no any file browser app, let's try to download one
+ final View customView = getLayoutInflater().inflate(R.layout.app_file_browser, null);
+ final ListView appsList = (ListView) customView.findViewById(android.R.id.list);
+ appsList.setAdapter(new FileBrowserAppsAdapter(this));
+ appsList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ appsList.setItemChecked(0, true);
+ new AlertDialog.Builder(this).setTitle(R.string.dfu_alert_no_filebrowser_title).setView(customView).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ dialog.dismiss();
+ }
+ }).setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ final int pos = appsList.getCheckedItemPosition();
+ if (pos >= 0) {
+ final String query = getResources().getStringArray(R.array.dfu_app_file_browser_action)[pos];
+ final Intent storeIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(query));
+ startActivity(storeIntent);
+ }
+ }
+ }).show();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (resultCode == Activity.RESULT_CANCELED)
+ return;
+
+ switch (requestCode) {
+ case SELECT_FILE_REQ: {
+ // clear previous data
+ final Uri uri = data.getData();
+ /*
+ * The URI returned from application may be in 'file' or 'content' schema.
+ * 'File' schema allows us to create a File object and read details from if directly.
+ * Data from 'Content' schema must be read with use of a Content Provider. To do that we are using a Loader.
+ */
+ if (uri.getScheme().equals("file")) {
+ // The direct path to the file has been returned
+ final String path = uri.getPath();
+ try {
+ final FileInputStream fis = new FileInputStream(path);
+ loadConfiguration(fis);
+ } catch (final FileNotFoundException e) {
+ Toast.makeText(this, R.string.uart_configuration_load_error, Toast.LENGTH_LONG).show();
+ }
+ } else if (uri.getScheme().equals("content")) {
+ // An Uri has been returned
+ Uri u = uri;
+
+ // If application returned Uri for streaming, let's us it. Does it works?
+ final Bundle extras = data.getExtras();
+ if (extras != null && extras.containsKey(Intent.EXTRA_STREAM))
+ u = extras.getParcelable(Intent.EXTRA_STREAM);
+
+ try {
+ final InputStream is = getContentResolver().openInputStream(u);
+ loadConfiguration(is);
+ } catch (final FileNotFoundException e) {
+ Toast.makeText(this, R.string.uart_configuration_load_error, Toast.LENGTH_LONG).show();
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ public void onCommandChanged(final int index, final String message, final boolean active, final int iconIndex) {
+ final Command command = mConfiguration.getCommands()[index];
+
+ command.setCommand(message);
+ command.setActive(active);
+ command.setIconIndex(iconIndex);
+ mConfigurationListener.onConfigurationModified();
+ saveConfiguration();
+ }
+
+ @Override
+ public void onNewConfiguration(final String name, final boolean duplicate) {
+ final boolean exists = mDatabaseHelper.configurationExists(name);
+ if (exists) {
+ Toast.makeText(this, R.string.uart_configuration_name_already_taken, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ UartConfiguration configuration = mConfiguration;
+ if (!duplicate)
+ configuration = new UartConfiguration();
+ configuration.setName(name);
+
+ try {
+ final Format format = new Format(new HyphenStyle());
+ final Strategy strategy = new VisitorStrategy(new CommentVisitor());
+ final Serializer serializer = new Persister(strategy, format);
+ final StringWriter writer = new StringWriter();
+ serializer.write(configuration, writer);
+ final String xml = writer.toString();
+
+ final long id = mDatabaseHelper.addConfiguration(name, xml);
+ refreshConfigurations();
+ selectConfiguration(mConfigurationsAdapter.getItemPosition(id));
+ } catch (final Exception e) {
+ Log.e(TAG, "Error while creating a new configuration", e);
+ }
+ }
+
+ @Override
+ public void onRenameConfiguration(final String newName) {
+ final boolean exists = mDatabaseHelper.configurationExists(newName);
+ if (exists) {
+ Toast.makeText(this, R.string.uart_configuration_name_already_taken, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ final String oldName = mConfiguration.getName();
+ mConfiguration.setName(newName);
+
+ try {
+ final Format format = new Format(new HyphenStyle());
+ final Strategy strategy = new VisitorStrategy(new CommentVisitor());
+ final Serializer serializer = new Persister(strategy, format);
+ final StringWriter writer = new StringWriter();
+ serializer.write(mConfiguration, writer);
+ final String xml = writer.toString();
+
+ mDatabaseHelper.renameConfiguration(oldName, newName, xml);
+ refreshConfigurations();
+ } catch (final Exception e) {
+ Log.e(TAG, "Error while renaming configuration", e);
+ }
+ }
+
+ private void refreshConfigurations() {
+ mConfigurationsAdapter.swapCursor(mDatabaseHelper.getServerConfigurationsNames());
+ mConfigurationsAdapter.notifyDataSetChanged();
+ invalidateOptionsMenu();
+ }
+
+ private void selectConfiguration(final int position) {
+ mConfigurationSpinner.setSelection(position);
+ }
+
/**
* Updates the ActionBar background color depending on whether we are in edit mode or not.
*
@@ -195,6 +575,7 @@ public void onBackPressed() {
@SuppressLint("NewApi")
private void setEditMode(final boolean editMode, final boolean change) {
mEditMode = editMode;
+ mConfigurationListener.setEditMode(editMode);
if (!change) {
final ColorDrawable color = new ColorDrawable();
int darkColor = 0;
@@ -238,4 +619,165 @@ public void onAnimationUpdate(final ValueAnimator animation) {
}
}
}
+
+ /**
+ * Saves the given configuration in the database.
+ */
+ private void saveConfiguration() {
+ final UartConfiguration configuration = mConfiguration;
+ try {
+ final Format format = new Format(new HyphenStyle());
+ final Strategy strategy = new VisitorStrategy(new CommentVisitor());
+ final Serializer serializer = new Persister(strategy, format);
+ final StringWriter writer = new StringWriter();
+ serializer.write(configuration, writer);
+ final String xml = writer.toString();
+
+ mDatabaseHelper.updateConfiguration(configuration.getName(), xml);
+ } catch (final Exception e) {
+ Log.e(TAG, "Error while creating a new configuration", e);
+ }
+ }
+
+ /**
+ * Loads the configuration from the given input stream.
+ * @param is the input stream
+ */
+ private void loadConfiguration(final InputStream is) {
+ try {
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ final StringBuilder builder = new StringBuilder();
+ for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+ builder.append(line).append("\n");
+ }
+ final String xml = builder.toString();
+
+ final Format format = new Format(new HyphenStyle());
+ final Serializer serializer = new Persister(format);
+ final UartConfiguration configuration = serializer.read(UartConfiguration.class, xml);
+
+ final String name = configuration.getName();
+ if (!mDatabaseHelper.configurationExists(name)) {
+ final long id = mDatabaseHelper.addConfiguration(name, xml);
+ refreshConfigurations();
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ selectConfiguration(mConfigurationsAdapter.getItemPosition(id));
+ }
+ });
+ } else {
+ Toast.makeText(this, R.string.uart_configuration_name_already_taken, Toast.LENGTH_LONG).show();
+ }
+ } catch (final Exception e) {
+ Log.e(TAG, "Loading configuration failed", e);
+
+ String message;
+ if (e.getLocalizedMessage() != null)
+ message = e.getLocalizedMessage();
+ else if (e.getCause() != null && e.getCause().getLocalizedMessage() != null)
+ message = e.getCause().getLocalizedMessage();
+ else
+ message = "Unknown error";
+ final String msg = message;
+ Snackbar.make(mSlider, R.string.uart_configuration_loading_failed, Snackbar.LENGTH_INDEFINITE).setAction(R.string.uart_action_details, new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ new AlertDialog.Builder(UARTActivity.this).setMessage(msg).setTitle(R.string.uart_action_details).setPositiveButton(R.string.ok, null).show();
+ }
+ }).show();
+ }
+ }
+
+ private void exportConfiguration() {
+ // TODO this may not work if the SD card is not available. (Lenovo A806, email from 11.03.2015)
+ final File folder = new File(Environment.getExternalStorageDirectory(), FileHelper.NORDIC_FOLDER);
+ if (!folder.exists())
+ folder.mkdir();
+ final File serverFolder = new File(folder, FileHelper.UART_FOLDER);
+ if (!serverFolder.exists())
+ serverFolder.mkdir();
+
+ final String fileName = mConfiguration.getName() + ".xml";
+ final File file = new File(serverFolder, fileName);
+ try {
+ file.createNewFile();
+ final FileOutputStream fos = new FileOutputStream(file);
+ final OutputStreamWriter writer = new OutputStreamWriter(fos);
+ writer.append(mDatabaseHelper.getConfiguration(mConfigurationSpinner.getSelectedItemId()));
+ writer.close();
+
+ // Notify user about the file
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.fromFile(file), "text/xml");
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(this, 420, intent, 0);
+ final Notification notification = new NotificationCompat.Builder(this).setContentIntent(pendingIntent).setContentTitle(fileName).setContentText(getText(R.string.uart_configuration_export_succeeded))
+ .setAutoCancel(true).setShowWhen(true).setTicker(getText(R.string.uart_configuration_export_succeeded_ticker)).setSmallIcon(android.R.drawable.stat_notify_sdcard).build();
+ final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(fileName, 823, notification);
+ } catch (final Exception e) {
+ Log.e(TAG, "Error while exporting configuration", e);
+ Toast.makeText(this, R.string.uart_configuration_save_error, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * Converts the old configuration, stored in preferences, into the first XML configuration and saves it to the database.
+ * If there is already any configuration in the database this method does nothing.
+ */
+ private void ensureFirstConfiguration(final DatabaseHelper mDatabaseHelper) {
+ // This method ensures that the "old", single configuration has been saved to the database.
+ if (mDatabaseHelper.getConfigurationsCount() == 0) {
+ final UartConfiguration configuration = new UartConfiguration();
+ configuration.setName("First configuration");
+ final Command[] commands = configuration.getCommands();
+
+ for (int i = 0; i < 9; ++i) {
+ final String cmd = mPreferences.getString(PREFS_BUTTON_COMMAND + i, null);
+ if (cmd != null) {
+ final Command command = new Command();
+ command.setCommand(cmd);
+ command.setActive(mPreferences.getBoolean(PREFS_BUTTON_ENABLED + i, false));
+ command.setIconIndex(mPreferences.getInt(PREFS_BUTTON_ICON + i, 0));
+ commands[i] = command;
+ }
+ }
+
+ try {
+ final Format format = new Format(new HyphenStyle());
+ final Strategy strategy = new VisitorStrategy(new CommentVisitor());
+ final Serializer serializer = new Persister(strategy, format);
+ final StringWriter writer = new StringWriter();
+ serializer.write(configuration, writer);
+ final String xml = writer.toString();
+
+ mDatabaseHelper.addConfiguration(configuration.getName(), xml);
+ } catch (final Exception e) {
+ Log.e(TAG, "Error while creating default configuration", e);
+ }
+ }
+ }
+
+ /**
+ * The comment visitor will add comments to the XML during saving.
+ */
+ private class CommentVisitor implements Visitor {
+ @Override
+ public void read(final Type type, final NodeMap node) throws Exception {
+ // do nothing
+ }
+
+ @Override
+ public void write(final Type type, final NodeMap node) throws Exception {
+ if (type.getType().equals(Command[].class)) {
+ OutputNode element = node.getNode();
+
+ StringBuilder builder = new StringBuilder("A configuration must have 9 commands, one for each button.\n Possible icons are:");
+ for (Command.Icon icon : Command.Icon.values())
+ builder.append("\n - ").append(icon.toString());
+ element.setComment(builder.toString());
+ }
+ }
+ }
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTButtonAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTButtonAdapter.java
index ad314ce65..7a25d8a1e 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTButtonAdapter.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTButtonAdapter.java
@@ -22,9 +22,6 @@
package no.nordicsemi.android.nrftoolbox.uart;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -32,21 +29,15 @@
import android.widget.ImageView;
import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.uart.domain.Command;
+import no.nordicsemi.android.nrftoolbox.uart.domain.UartConfiguration;
public class UARTButtonAdapter extends BaseAdapter {
- public final static String PREFS_BUTTON_ENABLED = "prefs_uart_enabled_";
- public final static String PREFS_BUTTON_COMMAND = "prefs_uart_command_";
- public final static String PREFS_BUTTON_ICON = "prefs_uart_icon_";
-
- private final SharedPreferences mPreferences;
- private final int[] mIcons;
- private final boolean[] mEnableFlags;
+ private UartConfiguration mConfiguration;
private boolean mEditMode;
- public UARTButtonAdapter(final Context context) {
- mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
- mIcons = new int[9];
- mEnableFlags = new boolean[9];
+ public UARTButtonAdapter(final UartConfiguration configuration) {
+ mConfiguration = configuration;
}
public void setEditMode(final boolean editMode) {
@@ -54,24 +45,19 @@ public void setEditMode(final boolean editMode) {
notifyDataSetChanged();
}
- @Override
- public void notifyDataSetChanged() {
- final SharedPreferences preferences = mPreferences;
- for (int i = 0; i < mIcons.length; ++i) {
- mIcons[i] = preferences.getInt(PREFS_BUTTON_ICON + i, -1);
- mEnableFlags[i] = preferences.getBoolean(PREFS_BUTTON_ENABLED + i, false);
- }
- super.notifyDataSetChanged();
+ public void setConfiguration(final UartConfiguration configuration) {
+ mConfiguration = configuration;
+ notifyDataSetChanged();
}
@Override
public int getCount() {
- return mIcons.length;
+ return mConfiguration != null ? mConfiguration.getCommands().length : 0;
}
@Override
public Object getItem(final int position) {
- return mIcons[position];
+ return mConfiguration.getCommands()[position];
}
@Override
@@ -91,7 +77,8 @@ public boolean areAllItemsEnabled() {
@Override
public boolean isEnabled(int position) {
- return mEditMode || mEnableFlags[position];
+ final Command command = (Command) getItem(position);
+ return mEditMode || (command != null && command.isActive());
}
@Override
@@ -105,9 +92,11 @@ public View getView(final int position, final View convertView, final ViewGroup
view.setActivated(mEditMode);
// Update image
+ final Command command = (Command) getItem(position);
final ImageView image = (ImageView) view;
- final int icon = mIcons[position];
- if (mEnableFlags[position] && icon != -1) {
+ final boolean active = command != null && command.isActive();
+ if (active) {
+ final int icon = command.getIconIndex();
image.setImageResource(R.drawable.uart_button);
image.setImageLevel(icon);
} else
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTConfigurationsAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTConfigurationsAdapter.java
new file mode 100644
index 000000000..7ec704c8e
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTConfigurationsAdapter.java
@@ -0,0 +1,138 @@
+/*************************************************************************************************************************************************
+ * Copyright (c) 2015, 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.nrftoolbox.uart;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import no.nordicsemi.android.nrftoolbox.R;
+
+public class UARTConfigurationsAdapter extends CursorAdapter {
+ final Context mContext;
+ final ActionListener mListener;
+
+ public interface ActionListener {
+ public void onNewConfigurationClick();
+ public void onImportClick();
+ }
+
+ public UARTConfigurationsAdapter(final Context context, final ActionListener listener, final Cursor c) {
+ super(context, c, 0);
+ mContext = context;
+ mListener = listener;
+ }
+
+ @Override
+ public int getCount() {
+ return super.getCount() + 1; // One for buttons at the top
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return super.getCount() == 0;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ if (position > 0)
+ return super.getItemId(position - 1);
+ return 0;
+ }
+
+ public int getItemPosition(final long id) {
+ final Cursor cursor = getCursor();
+ if (cursor == null)
+ return 1;
+
+ if (cursor.moveToFirst())
+ do {
+ if (cursor.getLong(0 /* _ID */) == id)
+ return cursor.getPosition() + 1;
+ } while (cursor.moveToNext());
+ return 1; // should never happen
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ if (position == 0) {
+ // This empty view should never be visible. Only positions 1+ are valid. Position 0 is reserved for action buttons.
+ // It is only created temporally when activity is created.
+ return LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_spinner_item, parent, false);
+ }
+ return super.getView(position - 1, convertView, parent);
+ }
+
+ @Override
+ public View getDropDownView(final int position, final View convertView, final ViewGroup parent) {
+ if (position == 0) {
+ return newToolbarView(mContext, parent);
+ }
+ if (convertView instanceof ViewGroup)
+ return super.getDropDownView(position - 1, null, parent);
+ return super.getDropDownView(position - 1, convertView, parent);
+ }
+
+ @Override
+ public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
+ return LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_spinner_item, parent, false);
+ }
+
+ @Override
+ public View newDropDownView(final Context context, final Cursor cursor, final ViewGroup parent) {
+ return LayoutInflater.from(mContext).inflate(R.layout.feature_uart_dropdown_item, parent, false);
+ }
+
+ public View newToolbarView(final Context context, final ViewGroup parent) {
+ final View view = LayoutInflater.from(context).inflate(R.layout.feature_uart_dropdown_title, parent, false);
+ view.findViewById(R.id.action_add).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mListener.onNewConfigurationClick();
+ }
+ });
+ view.findViewById(R.id.action_import).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mListener.onImportClick();
+ }
+ });
+ return view;
+ }
+
+ @Override
+ public void bindView(final View view, final Context context, final Cursor cursor) {
+ final String name = cursor.getString(1 /* NAME */);
+ ((TextView) view).setText(name);
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTControlFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTControlFragment.java
index ae89a990f..1435a4fb6 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTControlFragment.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTControlFragment.java
@@ -23,6 +23,7 @@
package no.nordicsemi.android.nrftoolbox.uart;
import android.app.Activity;
+import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
@@ -38,48 +39,43 @@
import android.widget.GridView;
import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.uart.domain.Command;
+import no.nordicsemi.android.nrftoolbox.uart.domain.UartConfiguration;
-public class UARTControlFragment extends Fragment implements GridView.OnItemClickListener {
+public class UARTControlFragment extends Fragment implements GridView.OnItemClickListener, UARTActivity.ConfigurationListener {
private final static String TAG = "UARTControlFragment";
private final static String SIS_EDIT_MODE = "sis_edit_mode";
- private ControlFragmentListener mListener;
- private SharedPreferences mPreferences;
+ private UartConfiguration mConfiguration;
private UARTButtonAdapter mAdapter;
private boolean mEditMode;
- public static interface ControlFragmentListener {
- public void setEditMode(final boolean editMode);
- }
-
@Override
- public void onAttach(final Activity activity) {
- super.onAttach(activity);
+ public void onAttach(final Context context) {
+ super.onAttach(context);
try {
- mListener = (ControlFragmentListener) activity;
+ ((UARTActivity)context).setConfigurationListener(this);
} catch (final ClassCastException e) {
Log.e(TAG, "The parent activity must implement EditModeListener");
}
}
- @Override
- public void onDetach() {
- super.onDetach();
- mListener = null;
- }
-
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
-
if (savedInstanceState != null) {
mEditMode = savedInstanceState.getBoolean(SIS_EDIT_MODE);
}
}
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ ((UARTActivity)getActivity()).setConfigurationListener(null);
+ }
+
@Override
public void onSaveInstanceState(final Bundle outState) {
outState.putBoolean(SIS_EDIT_MODE, mEditMode);
@@ -90,49 +86,42 @@ public View onCreateView(final LayoutInflater inflater, final ViewGroup containe
final View view = inflater.inflate(R.layout.fragment_feature_uart_control, container, false);
final GridView grid = (GridView) view.findViewById(R.id.grid);
- grid.setAdapter(mAdapter = new UARTButtonAdapter(getActivity()));
+ grid.setAdapter(mAdapter = new UARTButtonAdapter(mConfiguration));
grid.setOnItemClickListener(this);
mAdapter.setEditMode(mEditMode);
- setHasOptionsMenu(true);
return view;
}
@Override
public void onItemClick(final AdapterView> parent, final View view, final int position, final long id) {
if (mEditMode) {
- final UARTEditDialog dialog = UARTEditDialog.getInstance(position);
+ Command command = mConfiguration.getCommands()[position];
+ if (command == null)
+ mConfiguration.getCommands()[position] = command = new Command();
+ final UARTEditDialog dialog = UARTEditDialog.getInstance(position, command);
dialog.show(getChildFragmentManager(), null);
} else {
+ final String command = ((Command)mAdapter.getItem(position)).getCommand();
final UARTInterface uart = (UARTInterface) getActivity();
- uart.send(mPreferences.getString(UARTButtonAdapter.PREFS_BUTTON_COMMAND + position, ""));
+ uart.send(command);
}
}
@Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(mEditMode ? R.menu.uart_menu_config : R.menu.uart_menu, menu);
+ public void onConfigurationModified() {
+ mAdapter.notifyDataSetChanged();
}
@Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- final int itemId = item.getItemId();
- switch (itemId) {
- case R.id.action_configure:
- setEditMode(!mEditMode);
- return true;
- }
- return false;
+ public void onConfigurationChanged(final UartConfiguration configuration) {
+ mConfiguration = configuration;
+ mAdapter.setConfiguration(configuration);
}
+ @Override
public void setEditMode(final boolean editMode) {
mEditMode = editMode;
mAdapter.setEditMode(mEditMode);
- getActivity().invalidateOptionsMenu();
- mListener.setEditMode(mEditMode);
- }
-
- public void onConfigurationChanged() {
- mAdapter.notifyDataSetChanged();
}
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTEditDialog.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTEditDialog.java
index 1ecb16290..4b04a5fee 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTEditDialog.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTEditDialog.java
@@ -43,21 +43,25 @@
import android.widget.ImageView;
import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.uart.domain.Command;
public class UARTEditDialog extends DialogFragment implements View.OnClickListener, GridView.OnItemClickListener {
- private final static String TAG = "UARTEditDialog";
private final static String ARG_INDEX = "index";
+ private final static String ARG_COMMAND = "command";
+ private final static String ARG_ICON_INDEX = "iconIndex";
private int mActiveIcon;
private EditText mField;
private CheckBox mCheckBox;
private IconAdapter mIconAdapter;
- public static UARTEditDialog getInstance(final int index) {
+ public static UARTEditDialog getInstance(final int index, final Command command) {
final UARTEditDialog fragment = new UARTEditDialog();
final Bundle args = new Bundle();
args.putInt(ARG_INDEX, index);
+ args.putString(ARG_COMMAND, command.getCommand());
+ args.putInt(ARG_ICON_INDEX, command.getIconIndex());
fragment.setArguments(args);
return fragment;
@@ -66,15 +70,15 @@ public static UARTEditDialog getInstance(final int index) {
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
final LayoutInflater inflater = LayoutInflater.from(getActivity());
// Read button configuration
final Bundle args = getArguments();
final int index = args.getInt(ARG_INDEX);
- final String command = preferences.getString(UARTButtonAdapter.PREFS_BUTTON_COMMAND + index, null);
- final boolean active = true;//preferences.getBoolean(UARTButtonAdapter.PREFS_BUTTON_ENABLED + index, false);
- mActiveIcon = preferences.getInt(UARTButtonAdapter.PREFS_BUTTON_ICON + index, 0);
+ final String command = args.getString(ARG_COMMAND);
+ final int iconIndex = args.getInt(ARG_ICON_INDEX);
+ final boolean active = true; // change to active by default
+ mActiveIcon = iconIndex;
// Create view
final View view = inflater.inflate(R.layout.feature_uart_dialog_edit, null);
@@ -120,16 +124,9 @@ public void onClick(final View v) {
final Bundle args = getArguments();
final int index = args.getInt(ARG_INDEX);
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
- final SharedPreferences.Editor editor = preferences.edit();
- editor.putString(UARTButtonAdapter.PREFS_BUTTON_COMMAND + index, command);
- editor.putBoolean(UARTButtonAdapter.PREFS_BUTTON_ENABLED + index, active);
- editor.putInt(UARTButtonAdapter.PREFS_BUTTON_ICON + index, mActiveIcon);
- editor.apply();
-
dismiss();
- final UARTControlFragment parent = (UARTControlFragment) getParentFragment();
- parent.onConfigurationChanged();
+ final UARTActivity parent = (UARTActivity) getActivity();
+ parent.onCommandChanged(index, command, active, mActiveIcon);
}
@Override
@@ -167,6 +164,5 @@ public View getView(final int position, final View convertView, final ViewGroup
image.setActivated(position == mActiveIcon && mCheckBox.isChecked());
return view;
}
-
}
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManager.java
index 1f72f65cb..d0f6a43cb 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManager.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManager.java
@@ -139,6 +139,10 @@ protected boolean shouldAutoConnect() {
* @param text the text to be sent
*/
public void send(final String text) {
+ // Are we connected?
+ if (mRXCharacteristic == null)
+ return;
+
// An outgoing buffer may not be null if there is already another packet being sent. We do nothing in this case.
if (!TextUtils.isEmpty(text) && mOutgoingBuffer == null) {
final byte[] buffer = mOutgoingBuffer = text.getBytes();
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTNewConfigurationDialogFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTNewConfigurationDialogFragment.java
new file mode 100644
index 000000000..1c01e9a21
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTNewConfigurationDialogFragment.java
@@ -0,0 +1,142 @@
+/*************************************************************************************************************************************************
+ * Copyright (c) 2015, 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.nrftoolbox.uart;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+
+import no.nordicsemi.android.nrftoolbox.R;
+
+public class UARTNewConfigurationDialogFragment extends DialogFragment implements View.OnClickListener {
+ private static final String NAME = "name";
+ private static final String DUPLICATE = "duplicate";
+
+ private EditText mEditText;
+
+ private NewConfigurationDialogListener mListener;
+
+ public interface NewConfigurationDialogListener {
+ /**
+ * Creates a new configuration with given name.
+ * @param name the name
+ * @param duplicate true if configuration is to be duplicated
+ */
+ public void onNewConfiguration(final String name, final boolean duplicate);
+
+ /**
+ * Renames the current configuration with given name.
+ * @param newName the new name
+ */
+ public void onRenameConfiguration(final String newName);
+ }
+
+ @Override
+ public void onAttach(final Activity activity) {
+ super.onAttach(activity);
+
+ if (activity instanceof NewConfigurationDialogListener) {
+ mListener = (NewConfigurationDialogListener) activity;
+ } else {
+ throw new IllegalArgumentException("The parent activity must implement NewConfigurationDialogListener");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+
+ public static DialogFragment getInstance(final String name, final boolean duplicate) {
+ final DialogFragment dialog = new UARTNewConfigurationDialogFragment();
+
+ final Bundle args = new Bundle();
+ args.putString(NAME, name);
+ args.putBoolean(DUPLICATE, duplicate);
+ dialog.setArguments(args);
+
+ return dialog;
+ }
+
+ @Override
+ @NonNull
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final Bundle args = getArguments();
+ final String oldName = args.getString(NAME);
+ final boolean duplicate = args.getBoolean(DUPLICATE);
+ final int titleResId = duplicate || oldName == null ? R.string.uart_new_configuration_title : R.string.uart_rename_configuration_title;
+
+ final LayoutInflater inflater = LayoutInflater.from(getActivity());
+ final View view = inflater.inflate(R.layout.feature_uart_dialog_new_configuration, null);
+ final EditText editText = mEditText = (EditText) view.findViewById(R.id.name);
+ editText.setText(args.getString(NAME));
+ final View actionClear = view.findViewById(R.id.action_clear);
+ actionClear.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ editText.setText(null);
+ }
+ });
+
+ final AlertDialog dialog = new AlertDialog.Builder(context).setTitle(titleResId).setView(view).setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.ok, null).setCancelable(false).show(); // this must be show() or the getButton() below will return null.
+
+ final Button okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ okButton.setOnClickListener(this);
+
+ return dialog;
+ }
+
+ @Override
+ public void onClick(final View v) {
+ final String newName = mEditText.getText().toString().trim();
+ if (TextUtils.isEmpty(newName)) {
+ mEditText.setError(getString(R.string.uart_empty_name_error));
+ return;
+ }
+
+ final String oldName = getArguments().getString(NAME);
+ final boolean duplicate = getArguments().getBoolean(DUPLICATE);
+
+ if (duplicate || TextUtils.isEmpty(oldName))
+ mListener.onNewConfiguration(newName, duplicate);
+ else {
+ mListener.onRenameConfiguration(newName);
+ }
+ dismiss();
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java
index eb5478088..be14311d3 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java
@@ -30,6 +30,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.NotificationCompat;
import no.nordicsemi.android.log.ILogSession;
import no.nordicsemi.android.log.Logger;
@@ -158,11 +159,12 @@ private void createNotification(final int messageResId, final int defaults) {
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT);
- final Notification.Builder builder = new Notification.Builder(this).setContentIntent(pendingIntent);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+ builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_uart);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
- builder.addAction(R.drawable.ic_action_bluetooth, getString(R.string.uart_notification_action_disconnect), disconnectAction);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.uart_notification_action_disconnect), disconnectAction));
final Notification notification = builder.build();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/ConfigurationContract.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/ConfigurationContract.java
new file mode 100644
index 000000000..d78980197
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/ConfigurationContract.java
@@ -0,0 +1,38 @@
+/*************************************************************************************************************************************************
+ * Copyright (c) 2015, 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.nrftoolbox.uart.database;
+
+import android.provider.BaseColumns;
+
+public class ConfigurationContract {
+
+ protected interface ConfigurationColumns {
+ /** The XML with configuration. */
+ public final static String XML = "xml";
+ }
+
+ public final class Configuration implements BaseColumns, NameColumns, ConfigurationColumns, UndoColumns {
+ private Configuration() {
+ // empty
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/DatabaseHelper.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/DatabaseHelper.java
new file mode 100644
index 000000000..b0381ee64
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/DatabaseHelper.java
@@ -0,0 +1,226 @@
+/*************************************************************************************************************************************************
+ * Copyright (c) 2015, 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.nrftoolbox.uart.database;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+public class DatabaseHelper {
+ /** Database file name */
+ private static final String DATABASE_NAME = "toolbox_uart.db";
+ /** Database version */
+ private static final int DATABASE_VERSION = 1;
+
+ private interface Tables {
+ /** Configurations table. See {@link ConfigurationContract.Configuration} for column names. */
+ public static final String CONFIGURATIONS = "configurations";
+ }
+
+ private static final String[] ID_PROJECTION = new String[] { BaseColumns._ID };
+ private static final String[] NAME_PROJECTION = new String[] { BaseColumns._ID, NameColumns.NAME };
+ private static final String[] XML_PROJECTION = new String[] { BaseColumns._ID, ConfigurationContract.Configuration.XML };
+
+ private static final String ID_SELECTION = BaseColumns._ID + "=?";
+ private static final String NAME_SELECTION = NameColumns.NAME + "=?";
+ private static final String DELETED_SELECTION = UndoColumns.DELETED + "=1";
+ private static final String NOT_DELETED_SELECTION = UndoColumns.DELETED + "=0";
+
+ private static SQLiteHelper mDatabaseHelper;
+ private static SQLiteDatabase mDatabase;
+ private final ContentValues mValues = new ContentValues();
+ private final String[] mSingleArg = new String[1];
+
+ public DatabaseHelper(final Context context) {
+ if (mDatabaseHelper == null) {
+ mDatabaseHelper = new SQLiteHelper(context);
+ mDatabase = mDatabaseHelper.getWritableDatabase();
+ }
+ }
+
+ /**
+ * Returns number of saved configurations.
+ */
+ public int getConfigurationsCount() {
+ final Cursor cursor = mDatabase.query(Tables.CONFIGURATIONS, ID_PROJECTION, NOT_DELETED_SELECTION, null, null, null, null);
+ try {
+ return cursor.getCount();
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Returns the list of names of all saved configurations.
+ * @return cursor
+ */
+ public Cursor getServerConfigurationsNames() {
+ return mDatabase.query(Tables.CONFIGURATIONS, NAME_PROJECTION, NOT_DELETED_SELECTION, null, null, null, ConfigurationContract.Configuration.NAME + " ASC");
+ }
+
+ /**
+ * Returns the XML wth the configuration by id.
+ * @param id the configuration id in the DB
+ * @return the XML with configuration or null
+ */
+ public String getConfiguration(final long id) {
+ mSingleArg[0] = String.valueOf(id);
+
+ final Cursor cursor = mDatabase.query(Tables.CONFIGURATIONS, XML_PROJECTION, ID_SELECTION, mSingleArg, null, null, null);
+ try {
+ if (cursor.moveToNext())
+ return cursor.getString(1 /* XML */);
+ return null;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Adds new configuration to the database.
+ * @param name the configuration name
+ * @param configuration the XML
+ * @return the id or -1 if error occurred
+ */
+ public long addConfiguration(final String name, final String configuration) {
+ final ContentValues values = mValues;
+ values.clear();
+ values.put(ConfigurationContract.Configuration.NAME, name);
+ values.put(ConfigurationContract.Configuration.XML, configuration);
+ values.put(ConfigurationContract.Configuration.DELETED, 0);
+ return mDatabase.replace(Tables.CONFIGURATIONS, null, values);
+ }
+
+ /**
+ * Updates the configuration with the given name with the new XML
+ * @param name the configuration name to be updated
+ * @param configuration the new XML with configuration
+ * @return number of rows updated
+ */
+ public int updateConfiguration(final String name, final String configuration) {
+ mSingleArg[0] = name;
+
+ final ContentValues values = mValues;
+ values.clear();
+ values.put(ConfigurationContract.Configuration.XML, configuration);
+ values.put(ConfigurationContract.Configuration.DELETED, 0);
+ return mDatabase.update(Tables.CONFIGURATIONS, values, NAME_SELECTION, mSingleArg);
+ }
+
+ /**
+ * Marks the configuration with given name as deleted. If may be restored or removed permanently afterwards.
+ * @param name the configuration name
+ * @return number of rows affected
+ */
+ public int deleteConfiguration(final String name) {
+ mSingleArg[0] = name;
+
+ final ContentValues values = mValues;
+ values.clear();
+ values.put(ConfigurationContract.Configuration.DELETED, 1);
+ return mDatabase.update(Tables.CONFIGURATIONS, values, NAME_SELECTION, mSingleArg);
+ }
+
+ public int removeDeletedServerConfigurations() {
+ return mDatabase.delete(Tables.CONFIGURATIONS, DELETED_SELECTION, null);
+ }
+
+ public int restoreDeletedServerConfigurations() {
+ final ContentValues values = mValues;
+ values.clear();
+ values.put(ConfigurationContract.Configuration.DELETED, 0);
+ return mDatabase.update(Tables.CONFIGURATIONS, values, null, null);
+ }
+
+ /**
+ * Renames the server configuration and replaces its XML (name inside has changed).
+ * @param oldName the old name to look for
+ * @param newName the new configuration name
+ * @param configuration the new XML
+ * @return number of rows affected
+ */
+ public int renameConfiguration(final String oldName, final String newName, final String configuration) {
+ mSingleArg[0] = oldName;
+
+ final ContentValues values = mValues;
+ values.clear();
+ values.put(ConfigurationContract.Configuration.NAME, newName);
+ values.put(ConfigurationContract.Configuration.XML, configuration);
+ return mDatabase.update(Tables.CONFIGURATIONS, values, NAME_SELECTION, mSingleArg);
+ }
+
+ /**
+ * Returns true if a configuration with given name was found in the database.
+ * @param name the name to check
+ * @return true if such name exists, false otherwise
+ */
+ public boolean configurationExists(final String name) {
+ mSingleArg[0] = name;
+
+ final Cursor cursor = mDatabase.query(Tables.CONFIGURATIONS, NAME_PROJECTION, NAME_SELECTION + " AND " + NOT_DELETED_SELECTION, mSingleArg, null, null, null);
+ try {
+ return cursor.getCount() > 0;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private class SQLiteHelper extends SQLiteOpenHelper {
+
+ /**
+ * The SQL code that creates the Server Configurations:
+ *
+ *
+ */
+ private static final String CREATE_CONFIGURATIONS = "CREATE TABLE " + Tables.CONFIGURATIONS+ "(" + ConfigurationContract.Configuration._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + ConfigurationContract.Configuration.NAME + " TEXT UNIQUE NOT NULL, " + ConfigurationContract.Configuration.XML + " TEXT NOT NULL, " + ConfigurationContract.Configuration.DELETED +" INTEGER NOT NULL DEFAULT(0))";
+
+ private static final String DROP_IF_EXISTS = "DROP TABLE IF EXISTS ";
+
+ public SQLiteHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(final SQLiteDatabase db) {
+ db.execSQL(CREATE_CONFIGURATIONS);
+ }
+
+ @Override
+ public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ // This method does nothing for now.
+ switch (oldVersion) {
+ case 1:
+ // do nothing
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/NameColumns.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/NameColumns.java
new file mode 100644
index 000000000..a74678ec7
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/NameColumns.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2015, 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.nrftoolbox.uart.database;
+
+public interface NameColumns {
+ /** The name */
+ public final static String NAME = "name";
+}
\ No newline at end of file
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/UndoColumns.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/UndoColumns.java
new file mode 100644
index 000000000..eafacb324
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/database/UndoColumns.java
@@ -0,0 +1,27 @@
+/*************************************************************************************************************************************************
+ * Copyright (c) 2015, 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.nrftoolbox.uart.database;
+
+public interface UndoColumns {
+ /** The 'deleted' flag */
+ public final static String DELETED = "deleted";
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/domain/Command.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/domain/Command.java
new file mode 100644
index 000000000..6b4c13e1e
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/domain/Command.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2015, 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.nrftoolbox.uart.domain;
+
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.Root;
+import org.simpleframework.xml.Text;
+
+@Root
+public class Command {
+ public enum Icon {
+ LEFT(0),
+ UP(1),
+ RIGHT(2),
+ DOWN(3),
+ SETTINGS(4),
+ REW(5),
+ PLAY(6),
+ PAUSE(7),
+ STOP(8),
+ FWD(9),
+ INFO(10),
+ NUMBER_1(11),
+ NUMBER_2(12),
+ NUMBER_3(13),
+ NUMBER_4(14),
+ NUMBER_5(15),
+ NUMBER_6(16),
+ NUMBER_7(17),
+ NUMBER_8(18),
+ NUMBER_9(19);
+
+ public int index;
+
+ private Icon(final int index) {
+ this.index = index;
+ }
+ }
+
+ @Text(required = false)
+ private String command;
+
+ @Attribute(required = false)
+ private boolean active = false;
+
+ @Attribute(required = false)
+ private Icon icon = Icon.LEFT;
+
+ /**
+ * Sets the command.
+ * @param command the command that will be sent to UART device
+ */
+ public void setCommand(final String command) {
+ this.command = command;
+ }
+
+ /**
+ * Sets whether the command is active.
+ * @param active true to make it active
+ */
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ /**
+ * Sets the icon index.
+ * @param index index of the icon.
+ */
+ public void setIconIndex(final int index) {
+ this.icon = Icon.values()[index];
+ }
+
+ /**
+ * Returns the command that will be sent to UART device.
+ * @return the command
+ */
+ public String getCommand() {
+ return command;
+ }
+
+ /**
+ * Returns whether the icon is active.
+ * @return true if it's active
+ */
+ public boolean isActive() {
+ return active;
+ }
+
+ /**
+ * Returns the icon index.
+ * @return the icon index
+ */
+ public int getIconIndex() {
+ return icon.index;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/domain/UartConfiguration.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/domain/UartConfiguration.java
new file mode 100644
index 000000000..981f48ce7
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/domain/UartConfiguration.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2015, 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.nrftoolbox.uart.domain;
+
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.ElementArray;
+import org.simpleframework.xml.Root;
+import org.simpleframework.xml.core.PersistenceException;
+import org.simpleframework.xml.core.Validate;
+
+@Root
+public class UartConfiguration {
+ public static final int COMMANDS_COUNT = 9;
+
+ @Attribute(required = false, empty = "Unnamed")
+ private String name;
+
+ @ElementArray
+ private Command[] commands = new Command[COMMANDS_COUNT];
+
+ /**
+ * Returns the field name
+ *
+ * @return optional name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name to specified value
+ * @param name the new name
+ */
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the array of commands. There is always 9 of them.
+ * @return the commands array
+ */
+ public Command[] getCommands() {
+ return commands;
+ }
+
+ @Validate
+ private void validate() throws PersistenceException{
+ if (commands == null || commands.length != COMMANDS_COUNT)
+ throw new PersistenceException("There must be always " + COMMANDS_COUNT + " commands in a configuration.");
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/FileHelper.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/FileHelper.java
new file mode 100644
index 000000000..ed1f9c205
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/FileHelper.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2015, 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.nrftoolbox.utility;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+import android.preference.PreferenceManager;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import no.nordicsemi.android.nrftoolbox.R;
+
+public class FileHelper {
+ private static final String TAG = "FileHelper";
+
+ private static final String PREFS_SAMPLES_VERSION = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_SAMPLES_VERSION";
+ private static final int CURRENT_SAMPLES_VERSION = 4;
+
+ public static final String NORDIC_FOLDER = "Nordic Semiconductor";
+ public static final String UART_FOLDER = "UART Configurations";
+ public static final String BOARD_FOLDER = "Board";
+ public static final String BOARD_NRF6310_FOLDER = "nrf6310";
+ public static final String BOARD_PCA10028_FOLDER = "pca10028";
+ public static final String BOARD_PCA10036_FOLDER = "pca10036";
+
+ public static boolean newSamplesAvailable(final Context context) {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ final int version = preferences.getInt(PREFS_SAMPLES_VERSION, 0);
+ return version < CURRENT_SAMPLES_VERSION;
+ }
+
+ public static void createSamples(final Context context) {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ final int version = preferences.getInt(PREFS_SAMPLES_VERSION, 0);
+ if (version == CURRENT_SAMPLES_VERSION)
+ return;
+
+ /*
+ * Copy example HEX files to the external storage. Files will be copied if the DFU Applications folder is missing
+ */
+ final File root = new File(Environment.getExternalStorageDirectory(), "Nordic Semiconductor");
+ if (!root.exists()) {
+ root.mkdir();
+ }
+ final File board = new File(root, "Board");
+ if (!board.exists()) {
+ board.mkdir();
+ }
+ final File nrf6310 = new File(board, "nrf6310");
+ if (!nrf6310.exists()) {
+ nrf6310.mkdir();
+ }
+ final File pca10028 = new File(board, "pca10028");
+ if (!pca10028.exists()) {
+ pca10028.mkdir();
+ }
+
+ // Remove old files. Those will be moved to a new folder structure
+ new File(root, "ble_app_hrs_s110_v6_0_0.hex").delete();
+ new File(root, "ble_app_rscs_s110_v6_0_0.hex").delete();
+ new File(root, "ble_app_hrs_s110_v7_0_0.hex").delete();
+ new File(root, "ble_app_rscs_s110_v7_0_0.hex").delete();
+ new File(root, "blinky_arm_s110_v7_0_0.hex").delete();
+ new File(root, "dfu_2_0.bat").delete(); // This file has been migrated to 3.0
+ new File(root, "dfu_3_0.bat").delete(); // This file has been migrated to 3.1
+ new File(root, "dfu_2_0.sh").delete(); // This file has been migrated to 3.0
+ new File(root, "dfu_3_0.sh").delete(); // This file has been migrated to 3.1
+ new File(root, "README.txt").delete(); // This file has been modified to match v.3.0+
+
+ boolean oldCopied = false;
+ boolean newCopied = false;
+
+ // nrf6310 files
+ File f = new File(nrf6310, "ble_app_hrs_s110_v6_0_0.hex");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.ble_app_hrs_s110_v6_0_0, f);
+ oldCopied = true;
+ }
+ f = new File(nrf6310, "ble_app_rscs_s110_v6_0_0.hex");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.ble_app_rscs_s110_v6_0_0, f);
+ oldCopied = true;
+ }
+ f = new File(nrf6310, "ble_app_hrs_s110_v7_0_0.hex");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.ble_app_hrs_s110_v7_0_0, f);
+ oldCopied = true;
+ }
+ f = new File(nrf6310, "ble_app_rscs_s110_v7_0_0.hex");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.ble_app_rscs_s110_v7_0_0, f);
+ oldCopied = true;
+ }
+ f = new File(nrf6310, "blinky_arm_s110_v7_0_0.hex");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.blinky_arm_s110_v7_0_0, f);
+ oldCopied = true;
+ }
+ // PCA10028 files
+ f = new File(pca10028, "blinky_s110_v7_1_0.hex");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.blinky_s110_v7_1_0, f);
+ oldCopied = true;
+ }
+ f = new File(pca10028, "blinky_s110_v7_1_0_ext_init.dat");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.blinky_s110_v7_1_0_ext_init, f);
+ oldCopied = true;
+ }
+ f = new File(pca10028, "ble_app_hrs_dfu_s110_v7_1_0.hex");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.ble_app_hrs_dfu_s110_v7_1_0, f);
+ oldCopied = true;
+ }
+ f = new File(pca10028, "ble_app_hrs_dfu_s110_v7_1_0_ext_init.dat");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.ble_app_hrs_dfu_s110_v7_1_0_ext_init, f);
+ oldCopied = true;
+ }
+ new File(root, "ble_app_hrs_dfu_s110_v8_0_0.zip").delete(); // name changed
+ f = new File(pca10028, "ble_app_hrs_dfu_s110_v8_0_0_sdk_v8_0.zip");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.ble_app_hrs_dfu_s110_v8_0_0_sdk_v8_0, f);
+ newCopied = true;
+ }
+ f = new File(pca10028, "ble_app_hrs_dfu_s110_v8_0_0_sdk_v9_0.zip");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.ble_app_hrs_dfu_s110_v8_0_0_sdk_v9_0, f);
+ newCopied = true;
+ }
+ f = new File(pca10028, "ble_app_hrs_dfu_all_in_one_sdk_v9_0.zip");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.ble_app_hrs_dfu_all_in_one_sdk_v9_0, f);
+ newCopied = true;
+ }
+
+ if (oldCopied)
+ Toast.makeText(context, R.string.dfu_example_files_created, Toast.LENGTH_SHORT).show();
+ else if (newCopied)
+ Toast.makeText(context, R.string.dfu_example_new_files_created, Toast.LENGTH_SHORT).show();
+
+ // Scripts
+ newCopied = false;
+ f = new File(root, "dfu_3_1.bat");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.dfu_win_3_1, f);
+ newCopied = true;
+ }
+ f = new File(root, "dfu_3_1.sh");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.dfu_mac_3_1, f);
+ newCopied = true;
+ }
+ f = new File(root, "README.txt");
+ if (!f.exists()) {
+ copyRawResource(context, R.raw.readme, f);
+ }
+ if (newCopied)
+ Toast.makeText(context, R.string.dfu_scripts_created, Toast.LENGTH_SHORT).show();
+
+ // Save the current version
+ preferences.edit().putInt(PREFS_SAMPLES_VERSION, CURRENT_SAMPLES_VERSION).apply();
+ }
+
+ /**
+ * Copies the file from res/raw with given id to given destination file. If dest does not exist it will be created.
+ *
+ * @param context activity context
+ * @param rawResId the resource id
+ * @param dest destination file
+ */
+ private static void copyRawResource(final Context context, final int rawResId, final File dest) {
+ try {
+ final InputStream is = context.getResources().openRawResource(rawResId);
+ final FileOutputStream fos = new FileOutputStream(dest);
+
+ final byte[] buf = new byte[1024];
+ int read;
+ try {
+ while ((read = is.read(buf)) > 0)
+ fos.write(buf, 0, read);
+ } finally {
+ is.close();
+ fos.close();
+ }
+ } catch (final IOException e) {
+ DebugLogger.e(TAG, "Error while copying HEX file " + e.toString());
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/ClosableSpinner.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/ClosableSpinner.java
new file mode 100644
index 000000000..30a94d89b
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/ClosableSpinner.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015, 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.nrftoolbox.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Spinner;
+
+public class ClosableSpinner extends Spinner {
+ public ClosableSpinner(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void close() {
+ super.onDetachedFromWindow();
+ }
+}
diff --git a/app/src/main/res/drawable-hdpi/item_background_light_n.9.png b/app/src/main/res/drawable-hdpi/item_background_light_n.9.png
deleted file mode 100644
index 0c8a94514..000000000
Binary files a/app/src/main/res/drawable-hdpi/item_background_light_n.9.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/item_background_light_p.9.png b/app/src/main/res/drawable-hdpi/item_background_light_p.9.png
deleted file mode 100644
index 74947f36a..000000000
Binary files a/app/src/main/res/drawable-hdpi/item_background_light_p.9.png and /dev/null differ
diff --git a/app/src/main/res/drawable-v21/ic_icon_button_background.xml b/app/src/main/res/drawable-v21/ic_icon_button_background.xml
new file mode 100644
index 000000000..6b1e883bb
--- /dev/null
+++ b/app/src/main/res/drawable-v21/ic_icon_button_background.xml
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/app/src/main/res/drawable-xhdpi/item_background_light_n.9.png b/app/src/main/res/drawable-xhdpi/item_background_light_n.9.png
deleted file mode 100644
index 0c8a94514..000000000
Binary files a/app/src/main/res/drawable-xhdpi/item_background_light_n.9.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/item_background_light_p.9.png b/app/src/main/res/drawable-xhdpi/item_background_light_p.9.png
deleted file mode 100644
index 74947f36a..000000000
Binary files a/app/src/main/res/drawable-xhdpi/item_background_light_p.9.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_add_normal.png b/app/src/main/res/drawable-xxhdpi/ic_action_add_normal.png
new file mode 100644
index 000000000..9e16b1f72
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_add_normal.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_add_pressed.png b/app/src/main/res/drawable-xxhdpi/ic_action_add_pressed.png
new file mode 100644
index 000000000..e4b6217c4
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_add_pressed.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_clear_normal.png b/app/src/main/res/drawable-xxhdpi/ic_action_clear_normal.png
new file mode 100644
index 000000000..c03cb8790
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_clear_normal.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_clear_pressed.png b/app/src/main/res/drawable-xxhdpi/ic_action_clear_pressed.png
new file mode 100644
index 000000000..ed70b4e6b
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_clear_pressed.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_download_normal.png b/app/src/main/res/drawable-xxhdpi/ic_action_download_normal.png
new file mode 100644
index 000000000..bde2379db
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_download_normal.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_download_pressed.png b/app/src/main/res/drawable-xxhdpi/ic_action_download_pressed.png
new file mode 100644
index 000000000..2d91bc3fe
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_download_pressed.png differ
diff --git a/app/src/main/res/values-sw600dp-land-v21/dimens.xml b/app/src/main/res/drawable/ic_action_add.xml
similarity index 73%
rename from app/src/main/res/values-sw600dp-land-v21/dimens.xml
rename to app/src/main/res/drawable/ic_action_add.xml
index 5b0dcaf3e..b5f0f2589 100644
--- a/app/src/main/res/values-sw600dp-land-v21/dimens.xml
+++ b/app/src/main/res/drawable/ic_action_add.xml
@@ -1,4 +1,5 @@
-
-
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
-
- 56dp
- 0dp
+
+
-
+
diff --git a/app/src/main/res/values-land-v21/dimens.xml b/app/src/main/res/drawable/ic_action_clear.xml
similarity index 73%
rename from app/src/main/res/values-land-v21/dimens.xml
rename to app/src/main/res/drawable/ic_action_clear.xml
index 5b9851b3f..88047bc2f 100644
--- a/app/src/main/res/values-land-v21/dimens.xml
+++ b/app/src/main/res/drawable/ic_action_clear.xml
@@ -1,5 +1,5 @@
-
-
-
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
-
- 6dp
- 42dp
-
-
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_action_download.xml b/app/src/main/res/drawable/ic_action_download.xml
new file mode 100644
index 000000000..e0953c2b8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_action_download.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/activity_feature_bpm.xml b/app/src/main/res/layout-land/activity_feature_bpm.xml
index f6cefee97..6d6e34bf4 100644
--- a/app/src/main/res/layout-land/activity_feature_bpm.xml
+++ b/app/src/main/res/layout-land/activity_feature_bpm.xml
@@ -20,286 +20,287 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".BPMActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/activity_feature_csc.xml b/app/src/main/res/layout-land/activity_feature_csc.xml
index 62b33c0c8..2e3d4db3e 100644
--- a/app/src/main/res/layout-land/activity_feature_csc.xml
+++ b/app/src/main/res/layout-land/activity_feature_csc.xml
@@ -20,283 +20,284 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".CSCActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/activity_feature_dfu.xml b/app/src/main/res/layout-land/activity_feature_dfu.xml
index 4ba202526..f1f8ac68f 100644
--- a/app/src/main/res/layout-land/activity_feature_dfu.xml
+++ b/app/src/main/res/layout-land/activity_feature_dfu.xml
@@ -21,252 +21,253 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".DfuActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/activity_feature_gls.xml b/app/src/main/res/layout-land/activity_feature_gls.xml
index d87b2c402..ecf003cdb 100644
--- a/app/src/main/res/layout-land/activity_feature_gls.xml
+++ b/app/src/main/res/layout-land/activity_feature_gls.xml
@@ -20,207 +20,208 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".BPMActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/activity_feature_hrs.xml b/app/src/main/res/layout-land/activity_feature_hrs.xml
index a679e2a11..b849a7292 100644
--- a/app/src/main/res/layout-land/activity_feature_hrs.xml
+++ b/app/src/main/res/layout-land/activity_feature_hrs.xml
@@ -20,141 +20,142 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".BPMActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/activity_feature_hts.xml b/app/src/main/res/layout-land/activity_feature_hts.xml
index 71a5942dc..411bb44d0 100644
--- a/app/src/main/res/layout-land/activity_feature_hts.xml
+++ b/app/src/main/res/layout-land/activity_feature_hts.xml
@@ -20,100 +20,101 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".HTSActivity">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/activity_feature_proximity.xml b/app/src/main/res/layout-land/activity_feature_proximity.xml
index ce1e6ef0b..71b486b90 100644
--- a/app/src/main/res/layout-land/activity_feature_proximity.xml
+++ b/app/src/main/res/layout-land/activity_feature_proximity.xml
@@ -20,114 +20,115 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".ProximityActivity">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/activity_feature_rsc.xml b/app/src/main/res/layout-land/activity_feature_rsc.xml
index 3af7e01e4..18250345a 100644
--- a/app/src/main/res/layout-land/activity_feature_rsc.xml
+++ b/app/src/main/res/layout-land/activity_feature_rsc.xml
@@ -20,313 +20,314 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".RSCActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/activity_feature_uart.xml b/app/src/main/res/layout-land/activity_feature_uart.xml
index b1bd02c1a..b2435bbf7 100644
--- a/app/src/main/res/layout-land/activity_feature_uart.xml
+++ b/app/src/main/res/layout-land/activity_feature_uart.xml
@@ -20,46 +20,47 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".UARTActivity">
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/feature_uart_button.xml b/app/src/main/res/layout-land/feature_uart_button.xml
index 25e6c5c40..fd12f3760 100644
--- a/app/src/main/res/layout-land/feature_uart_button.xml
+++ b/app/src/main/res/layout-land/feature_uart_button.xml
@@ -20,10 +20,10 @@
~ 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.
-->
-
+
diff --git a/app/src/main/res/layout-land/fragment_feature_uart_control.xml b/app/src/main/res/layout-land/fragment_feature_uart_control.xml
index df639f85e..70ab3eeb5 100644
--- a/app/src/main/res/layout-land/fragment_feature_uart_control.xml
+++ b/app/src/main/res/layout-land/fragment_feature_uart_control.xml
@@ -20,84 +20,84 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/background_image"
+ android:orientation="vertical">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+ android:layout_marginBottom="6dp"
+ android:src="@drawable/background_title"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout-sw600dp-land/activity_feature_bpm.xml b/app/src/main/res/layout-sw600dp-land/activity_feature_bpm.xml
index 3aa8760c8..780a605bd 100644
--- a/app/src/main/res/layout-sw600dp-land/activity_feature_bpm.xml
+++ b/app/src/main/res/layout-sw600dp-land/activity_feature_bpm.xml
@@ -20,287 +20,288 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".BPMActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-sw600dp-land/activity_feature_csc.xml b/app/src/main/res/layout-sw600dp-land/activity_feature_csc.xml
index a85b5ac52..3db8a67d9 100644
--- a/app/src/main/res/layout-sw600dp-land/activity_feature_csc.xml
+++ b/app/src/main/res/layout-sw600dp-land/activity_feature_csc.xml
@@ -20,245 +20,246 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".CSCActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-sw600dp-land/activity_feature_dfu.xml b/app/src/main/res/layout-sw600dp-land/activity_feature_dfu.xml
index 25e184480..215422940 100644
--- a/app/src/main/res/layout-sw600dp-land/activity_feature_dfu.xml
+++ b/app/src/main/res/layout-sw600dp-land/activity_feature_dfu.xml
@@ -21,245 +21,246 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".DfuActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-sw600dp-land/activity_feature_hrs.xml b/app/src/main/res/layout-sw600dp-land/activity_feature_hrs.xml
index c3b2858e1..fc3ec82f6 100644
--- a/app/src/main/res/layout-sw600dp-land/activity_feature_hrs.xml
+++ b/app/src/main/res/layout-sw600dp-land/activity_feature_hrs.xml
@@ -20,142 +20,143 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".BPMActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-sw600dp-land/feature_uart_button.xml b/app/src/main/res/layout-sw600dp-land/feature_uart_button.xml
index 50ce8af14..87e34bb5e 100644
--- a/app/src/main/res/layout-sw600dp-land/feature_uart_button.xml
+++ b/app/src/main/res/layout-sw600dp-land/feature_uart_button.xml
@@ -20,9 +20,9 @@
~ 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.
-->
-
+
diff --git a/app/src/main/res/layout-sw720dp-land/activity_feature_rsc.xml b/app/src/main/res/layout-sw720dp-land/activity_feature_rsc.xml
index 99c3b6ecc..628a8ba62 100644
--- a/app/src/main/res/layout-sw720dp-land/activity_feature_rsc.xml
+++ b/app/src/main/res/layout-sw720dp-land/activity_feature_rsc.xml
@@ -20,280 +20,281 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".RSCActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-v21/feature_icon.xml b/app/src/main/res/layout-v21/feature_icon.xml
index e295b997b..419556b68 100644
--- a/app/src/main/res/layout-v21/feature_icon.xml
+++ b/app/src/main/res/layout-v21/feature_icon.xml
@@ -21,26 +21,26 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingTop="7dp">
-
+
-
+
diff --git a/app/src/main/res/layout-v21/fragment_device_selection.xml b/app/src/main/res/layout-v21/fragment_device_selection.xml
index ab7becf61..fca1c2718 100644
--- a/app/src/main/res/layout-v21/fragment_device_selection.xml
+++ b/app/src/main/res/layout-v21/fragment_device_selection.xml
@@ -20,29 +20,29 @@
~ 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.
-->
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-v23/fragment_device_selection.xml b/app/src/main/res/layout-v23/fragment_device_selection.xml
new file mode 100644
index 000000000..28b118a53
--- /dev/null
+++ b/app/src/main/res/layout-v23/fragment_device_selection.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_feature_bpm.xml b/app/src/main/res/layout/activity_feature_bpm.xml
index fbb5ed445..5e8361359 100644
--- a/app/src/main/res/layout/activity_feature_bpm.xml
+++ b/app/src/main/res/layout/activity_feature_bpm.xml
@@ -20,274 +20,275 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".BPMActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_feature_csc.xml b/app/src/main/res/layout/activity_feature_csc.xml
index a85b5ac52..3db8a67d9 100644
--- a/app/src/main/res/layout/activity_feature_csc.xml
+++ b/app/src/main/res/layout/activity_feature_csc.xml
@@ -20,245 +20,246 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".CSCActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_feature_dfu.xml b/app/src/main/res/layout/activity_feature_dfu.xml
index e879be913..0b66270b4 100644
--- a/app/src/main/res/layout/activity_feature_dfu.xml
+++ b/app/src/main/res/layout/activity_feature_dfu.xml
@@ -21,246 +21,247 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".DfuActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_feature_gls.xml b/app/src/main/res/layout/activity_feature_gls.xml
index 7f0012bec..e7ebcad2d 100644
--- a/app/src/main/res/layout/activity_feature_gls.xml
+++ b/app/src/main/res/layout/activity_feature_gls.xml
@@ -20,205 +20,206 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_feature_gls_item.xml b/app/src/main/res/layout/activity_feature_gls_item.xml
index 86e1d8a55..658bf1b62 100644
--- a/app/src/main/res/layout/activity_feature_gls_item.xml
+++ b/app/src/main/res/layout/activity_feature_gls_item.xml
@@ -21,34 +21,34 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
+ android:paddingBottom="2dp"
+ android:paddingEnd="4dp"
+ android:paddingStart="?android:attr/expandableListPreferredItemPaddingLeft"
+ android:paddingTop="2dp">
-
+
-
+
-
+
diff --git a/app/src/main/res/layout/activity_feature_gls_subitem.xml b/app/src/main/res/layout/activity_feature_gls_subitem.xml
index 6e9e29b66..1440d2055 100644
--- a/app/src/main/res/layout/activity_feature_gls_subitem.xml
+++ b/app/src/main/res/layout/activity_feature_gls_subitem.xml
@@ -21,26 +21,26 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingBottom="2dp"
+ android:paddingStart="?android:attr/expandableListPreferredItemPaddingLeft"
+ android:paddingTop="2dp">
-
+
-
+
diff --git a/app/src/main/res/layout/activity_feature_hrs.xml b/app/src/main/res/layout/activity_feature_hrs.xml
index dcc1a5bc4..bda6107b8 100644
--- a/app/src/main/res/layout/activity_feature_hrs.xml
+++ b/app/src/main/res/layout/activity_feature_hrs.xml
@@ -20,135 +20,136 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".HRSActivity">
-
+
-
-
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
diff --git a/app/src/main/res/layout/activity_feature_hts.xml b/app/src/main/res/layout/activity_feature_hts.xml
index 71a5942dc..411bb44d0 100644
--- a/app/src/main/res/layout/activity_feature_hts.xml
+++ b/app/src/main/res/layout/activity_feature_hts.xml
@@ -20,100 +20,101 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".HTSActivity">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_feature_proximity.xml b/app/src/main/res/layout/activity_feature_proximity.xml
index 109c13001..3372f2331 100644
--- a/app/src/main/res/layout/activity_feature_proximity.xml
+++ b/app/src/main/res/layout/activity_feature_proximity.xml
@@ -20,109 +20,110 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".ProximityActivity">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_feature_rsc.xml b/app/src/main/res/layout/activity_feature_rsc.xml
index 99c3b6ecc..628a8ba62 100644
--- a/app/src/main/res/layout/activity_feature_rsc.xml
+++ b/app/src/main/res/layout/activity_feature_rsc.xml
@@ -20,280 +20,281 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".RSCActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_feature_template.xml b/app/src/main/res/layout/activity_feature_template.xml
index 1ac1b86c2..9f92eb59e 100644
--- a/app/src/main/res/layout/activity_feature_template.xml
+++ b/app/src/main/res/layout/activity_feature_template.xml
@@ -20,135 +20,136 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".TemplateActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_feature_uart.xml b/app/src/main/res/layout/activity_feature_uart.xml
index 513b49501..e82921b1c 100644
--- a/app/src/main/res/layout/activity_feature_uart.xml
+++ b/app/src/main/res/layout/activity_feature_uart.xml
@@ -20,39 +20,40 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".UARTActivity">
-
+
-
+
-
+
-
+
-
+
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_features.xml b/app/src/main/res/layout/activity_features.xml
index 3cb702ba7..453f3ef60 100644
--- a/app/src/main/res/layout/activity_features.xml
+++ b/app/src/main/res/layout/activity_features.xml
@@ -20,75 +20,76 @@
~ 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.
-->
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index e624738f3..254b68c2d 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -27,22 +27,22 @@
android:orientation="vertical">
+ android:id="@+id/toolbar_actionbar"
+ layout="@layout/toolbar"/>
-
+
+ android:layout_height="match_parent"
+ android:layout_below="@+id/toolbar_actionbar"/>
diff --git a/app/src/main/res/layout/activity_splashscreen.xml b/app/src/main/res/layout/activity_splashscreen.xml
index 8d26b46a1..4eed171f5 100644
--- a/app/src/main/res/layout/activity_splashscreen.xml
+++ b/app/src/main/res/layout/activity_splashscreen.xml
@@ -20,24 +20,24 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".SplashscreenActivity">
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/app_file_browser.xml b/app/src/main/res/layout/app_file_browser.xml
index 9d91dae47..b405b567b 100644
--- a/app/src/main/res/layout/app_file_browser.xml
+++ b/app/src/main/res/layout/app_file_browser.xml
@@ -21,23 +21,23 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
-
+
-
+
diff --git a/app/src/main/res/layout/app_file_browser_item.xml b/app/src/main/res/layout/app_file_browser_item.xml
index 662e638d7..0e8169db6 100644
--- a/app/src/main/res/layout/app_file_browser_item.xml
+++ b/app/src/main/res/layout/app_file_browser_item.xml
@@ -19,17 +19,17 @@
~ 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.
-->
-
+
diff --git a/app/src/main/res/layout/device_list_empty.xml b/app/src/main/res/layout/device_list_empty.xml
index c4a12eb7c..03383ae75 100644
--- a/app/src/main/res/layout/device_list_empty.xml
+++ b/app/src/main/res/layout/device_list_empty.xml
@@ -21,7 +21,7 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:text="@string/scanner_empty"/>
diff --git a/app/src/main/res/layout/device_list_row.xml b/app/src/main/res/layout/device_list_row.xml
index b9e23f3cb..92713bfa2 100644
--- a/app/src/main/res/layout/device_list_row.xml
+++ b/app/src/main/res/layout/device_list_row.xml
@@ -21,35 +21,35 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
+ android:orientation="vertical"
+ android:padding="6dp">
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/device_list_title.xml b/app/src/main/res/layout/device_list_title.xml
index 2bdaa6360..3d5da0069 100644
--- a/app/src/main/res/layout/device_list_title.xml
+++ b/app/src/main/res/layout/device_list_title.xml
@@ -20,7 +20,7 @@
~ 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.
-->
-
+
diff --git a/app/src/main/res/layout/drawer.xml b/app/src/main/res/layout/drawer.xml
index baf5587de..c2e4e35c0 100644
--- a/app/src/main/res/layout/drawer.xml
+++ b/app/src/main/res/layout/drawer.xml
@@ -55,7 +55,7 @@
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingEnd="@dimen/navdrawer_padding_horiz"
+ android:paddingStart="@dimen/navdrawer_padding_horiz">
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/expandable_list_content.xml b/app/src/main/res/layout/expandable_list_content.xml
index 799ea5a71..f2761cfc5 100644
--- a/app/src/main/res/layout/expandable_list_content.xml
+++ b/app/src/main/res/layout/expandable_list_content.xml
@@ -17,8 +17,8 @@
** limitations under the License.
*/
-->
-
+
diff --git a/app/src/main/res/layout/feature_icon.xml b/app/src/main/res/layout/feature_icon.xml
index 245f7fe07..7b3535039 100644
--- a/app/src/main/res/layout/feature_icon.xml
+++ b/app/src/main/res/layout/feature_icon.xml
@@ -21,24 +21,24 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingTop="7dp">
-
+
-
+
diff --git a/app/src/main/res/layout/feature_uart_button.xml b/app/src/main/res/layout/feature_uart_button.xml
index 50ce8af14..87e34bb5e 100644
--- a/app/src/main/res/layout/feature_uart_button.xml
+++ b/app/src/main/res/layout/feature_uart_button.xml
@@ -20,9 +20,9 @@
~ 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.
-->
-
+
diff --git a/app/src/main/res/layout/feature_uart_dialog_edit.xml b/app/src/main/res/layout/feature_uart_dialog_edit.xml
index 37cae0d9f..25e6a450d 100644
--- a/app/src/main/res/layout/feature_uart_dialog_edit.xml
+++ b/app/src/main/res/layout/feature_uart_dialog_edit.xml
@@ -21,40 +21,43 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
-
-
-
-
-
-
-
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="8dp">
-
+
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/feature_uart_dialog_edit_icon.xml b/app/src/main/res/layout/feature_uart_dialog_edit_icon.xml
index 139c72390..b35b3adcf 100644
--- a/app/src/main/res/layout/feature_uart_dialog_edit_icon.xml
+++ b/app/src/main/res/layout/feature_uart_dialog_edit_icon.xml
@@ -21,8 +21,8 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/uart_dialog_button_background"
+ android:scaleType="center"
+ android:src="@drawable/uart_button_small"/>
diff --git a/app/src/main/res/layout/feature_uart_dialog_new_configuration.xml b/app/src/main/res/layout/feature_uart_dialog_new_configuration.xml
new file mode 100644
index 000000000..9a61f9ad1
--- /dev/null
+++ b/app/src/main/res/layout/feature_uart_dialog_new_configuration.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/feature_uart_dropdown_item.xml b/app/src/main/res/layout/feature_uart_dropdown_item.xml
new file mode 100644
index 000000000..3c8637d7f
--- /dev/null
+++ b/app/src/main/res/layout/feature_uart_dropdown_item.xml
@@ -0,0 +1,31 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/feature_uart_dropdown_title.xml b/app/src/main/res/layout/feature_uart_dropdown_title.xml
new file mode 100644
index 000000000..31cb343e0
--- /dev/null
+++ b/app/src/main/res/layout/feature_uart_dropdown_title.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_device_selection.xml b/app/src/main/res/layout/fragment_device_selection.xml
index 262ad8edf..ba606a1d6 100644
--- a/app/src/main/res/layout/fragment_device_selection.xml
+++ b/app/src/main/res/layout/fragment_device_selection.xml
@@ -20,30 +20,30 @@
~ 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.
-->
-
+
-
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_feature_uart_control.xml b/app/src/main/res/layout/fragment_feature_uart_control.xml
index 8ab45d371..70ab3eeb5 100644
--- a/app/src/main/res/layout/fragment_feature_uart_control.xml
+++ b/app/src/main/res/layout/fragment_feature_uart_control.xml
@@ -20,84 +20,84 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:orientation="vertical">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+ android:layout_marginBottom="6dp"
+ android:src="@drawable/background_title"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_feature_uart_log.xml b/app/src/main/res/layout/fragment_feature_uart_log.xml
index 60a01899c..ad7734061 100644
--- a/app/src/main/res/layout/fragment_feature_uart_log.xml
+++ b/app/src/main/res/layout/fragment_feature_uart_log.xml
@@ -20,50 +20,50 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#FFFFFF"
+ android:orientation="vertical">
-
+
-
+
-
+
-
+
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_zip_info.xml b/app/src/main/res/layout/fragment_zip_info.xml
index 527be066a..f266a291d 100644
--- a/app/src/main/res/layout/fragment_zip_info.xml
+++ b/app/src/main/res/layout/fragment_zip_info.xml
@@ -21,17 +21,17 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/log_item.xml b/app/src/main/res/layout/log_item.xml
index 0734866ff..b613bd938 100644
--- a/app/src/main/res/layout/log_item.xml
+++ b/app/src/main/res/layout/log_item.xml
@@ -21,20 +21,20 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
-
+
-
+
diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml
index c44c1c841..58fb81d5a 100644
--- a/app/src/main/res/layout/toolbar.xml
+++ b/app/src/main/res/layout/toolbar.xml
@@ -21,10 +21,10 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu-port/uart_menu.xml b/app/src/main/res/menu-port/uart_menu.xml
index b828bf6e9..e58d01727 100644
--- a/app/src/main/res/menu-port/uart_menu.xml
+++ b/app/src/main/res/menu-port/uart_menu.xml
@@ -20,15 +20,18 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
diff --git a/app/src/main/res/menu-port/uart_menu_config.xml b/app/src/main/res/menu-port/uart_menu_config.xml
index 9325bf564..cace521e8 100644
--- a/app/src/main/res/menu-port/uart_menu_config.xml
+++ b/app/src/main/res/menu-port/uart_menu_config.xml
@@ -24,11 +24,13 @@
diff --git a/app/src/main/res/menu/uart_menu.xml b/app/src/main/res/menu/uart_menu.xml
index 5028e3a08..0400350b3 100644
--- a/app/src/main/res/menu/uart_menu.xml
+++ b/app/src/main/res/menu/uart_menu.xml
@@ -20,11 +20,12 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
diff --git a/app/src/main/res/menu/uart_menu_config.xml b/app/src/main/res/menu/uart_menu_config.xml
index f06bda8a5..1a17bbc50 100644
--- a/app/src/main/res/menu/uart_menu_config.xml
+++ b/app/src/main/res/menu/uart_menu_config.xml
@@ -20,11 +20,12 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
diff --git a/app/src/main/res/menu/uart_menu_configurations.xml b/app/src/main/res/menu/uart_menu_configurations.xml
new file mode 100644
index 000000000..57437762e
--- /dev/null
+++ b/app/src/main/res/menu/uart_menu_configurations.xml
@@ -0,0 +1,55 @@
+
+
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
index 2de3b4d7e..4010538c1 100644
--- a/app/src/main/res/values-land/dimens.xml
+++ b/app/src/main/res/values-land/dimens.xml
@@ -26,8 +26,4 @@
40dp238dp178dp
-
-
- 6dp
- 0dp
diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml
index 9482d4a2e..5ef32265c 100644
--- a/app/src/main/res/values-sw600dp/dimens.xml
+++ b/app/src/main/res/values-sw600dp/dimens.xml
@@ -37,6 +37,4 @@
256dp256dp
- -87dp
-
diff --git a/app/src/main/res/values-v21/dimens.xml b/app/src/main/res/values-v21/dimens.xml
index 11f1d5a6a..3757ccc7a 100644
--- a/app/src/main/res/values-v21/dimens.xml
+++ b/app/src/main/res/values-v21/dimens.xml
@@ -22,11 +22,7 @@
-->
-
- 56dp
- -48dp6dp
-
54dp
diff --git a/app/src/main/res/values-v21/styles_icon_button.xml b/app/src/main/res/values-v21/styles_icon_button.xml
new file mode 100644
index 000000000..015ef3266
--- /dev/null
+++ b/app/src/main/res/values-v21/styles_icon_button.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index ce9a21064..35384aef9 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -36,12 +36,6 @@
30dp16dp
-
- 10dp
- 0dp
- 0dp
- -81dp
-
24dp18dp0dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 012fa3bfe..cbee8212e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -22,49 +22,53 @@
-->
- nRF Toolbox
- fonts/trebucbd.ttf
- fonts/trebuc.ttf
-
- OK
- Yes
- No
- Cancel
-
- About
- Settings
- CONNECT
- SELECT DEVICE
- DISCONNECT
-
- Open
- Close
- PLUGINS
- nRF Master Control Panel
-
- %d%%
- %d%%
- n/a
- " - "
- Device doesn\'t have BLE support!
- No profiles found.
- The device does not have required services.
- Web Browser application is not available.
- Google Play not installed.
- Select device:
- No device found
- Scan
- Cancel
- BONDED DEVICES:
- AVAILABLE DEVICES:
-
- Bonding with the device…
- The device is now bonded.
-
- %1$tR:%1$tS.%1$tL
-
- About
- \n\nVersion: %s
- The nRF Toolbox is a container application that stores your Nordic Semiconductor apps in one location.
+ nRF Toolbox
+ fonts/trebucbd.ttf
+ fonts/trebuc.ttf
+
+ OK
+ Yes
+ No
+ Cancel
+
+ About
+ Settings
+ CONNECT
+ SELECT DEVICE
+ DISCONNECT
+
+ Open
+ Close
+ PLUGINS
+ nRF Master Control Panel
+
+ %d%%
+ n/a
+ " - "
+ Device doesn\'t have BLE support!
+ No profiles found.
+ The device does not have required services.
+ Web Browser application is not available.
+ There is no application that can open this URI.
+ Google Play not installed.
+ Permission denied.
+ Select device:
+ No device found
+ Scan
+ Cancel
+ BONDED DEVICES:
+ AVAILABLE DEVICES:
+ Since Android 6.0 Marshmallow system requires granting access to device\'s location in order to scan for Bluetooth Smart devices. Bluetooth beacons may be used to determine the phone\'s and user\'s location.
+
+ Bonding with the device…
+ The device is now bonded.
+
+ %1$tR:%1$tS.%1$tL
+
+ Permission required
+ nRF Toolbox would like to copy some sample applications, that may be uploaded onto your nRF51 device using the DFU (Device Firmware Update), to the memory of this device. If you agree, you will be asked to give the application permission to access files on your device.
+ About
+ \n\nVersion: %s
+ The nRF Toolbox is a container application that stores your Nordic Semiconductor apps in one location.
diff --git a/app/src/main/res/values/strings_dfu.xml b/app/src/main/res/values/strings_dfu.xml
index 1364d6d77..7d355a8ad 100644
--- a/app/src/main/res/values/strings_dfu.xml
+++ b/app/src/main/res/values/strings_dfu.xml
@@ -65,14 +65,13 @@
DEVICE FIRMWARE UPDATEUPLOAD
- \?CANCELDFU optionsPackets receipt notification procedureNumber of packetsMBR size
- MBR size (default 4096 = 0x1000)
+ MBR size (4096 on nRF51, 12288 on nRF52)Keep bond informationAbout DFUDFU documentation on Nordic\'s Developer Zone
@@ -100,7 +99,6 @@
%d%%DEFAULT DFUApplication Uploading
- Are you sure to exit?Are you sure to cancel upload?Application has been transferred successfully.Uploading of the application has been canceled.
diff --git a/app/src/main/res/values/strings_uart.xml b/app/src/main/res/values/strings_uart.xml
index dbafb24e4..d4fac1a60 100644
--- a/app/src/main/res/values/strings_uart.xml
+++ b/app/src/main/res/values/strings_uart.xml
@@ -28,12 +28,32 @@
Write commandSENDShow log
+ Share
+ Export to XML
+ Rename
+ Duplicate
+ Delete
+ Details
+ UndoUART-24dpDEFAULT UARTNo data to display.
+
+ Rename configuration
+ New GATT configuration
+ Configuration name
+ Please, provide a unique configuration name:
+ Name must not be empty.
+ Loading configuration failed.
+ Configuration with this name already exists.
+ Configuration removed.
+ Export failed.
+ Import failed.
+ File saved
+ Configuration saved in /sdcard/Nordic SemiconductorConfigure buttonEnter command
@@ -46,7 +66,7 @@
UART profile (Universal Asynchronous Receiver and Transmitter) is a Bluetooth Smart implementation of the UART standard. It allows for bidirectional
text based communication that is often used for debugging and control.
- \nThe nRF Toolbox UART profile main screen contains 9 programmable buttons that may send commands to the UART TX characteristic. Click on the EDIT menu item to edit buttons actions.
- \nScroll the main pane right, or rotate your device to the landscape orientation to see the oncoming and incoming data as well as other connection events. If nRF Logger application
+ \nEach configuration contains 9 programmable buttons that may send commands to the UART TX characteristic. Click on the EDIT menu item to edit buttons actions.
+ \nScroll the main pane right, or rotate your device to the landscape orientation to see the oncoming and incoming data as well as other connection events.\n\nIf nRF Logger application
is installed, the log is stored in the application\'s database.
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index ae8fa74ce..075ae4f02 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -21,121 +21,127 @@
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/styles_icon_button.xml b/app/src/main/res/values/styles_icon_button.xml
new file mode 100644
index 000000000..36884a3e2
--- /dev/null
+++ b/app/src/main/res/values/styles_icon_button.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file