diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..db1555f09
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/dictionaries/*.xml
+/.idea/libraries
+.DS_Store
+/build
+/key*
\ No newline at end of file
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 000000000..41bcebb2a
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+nRFToolbox
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 000000000..217af471a
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/copyright/BSD.xml b/.idea/copyright/BSD.xml
new file mode 100644
index 000000000..178eefff0
--- /dev/null
+++ b/.idea/copyright/BSD.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 000000000..564653fff
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 000000000..e206d70d8
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 000000000..cff9e6e6b
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 000000000..59436c989
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 000000000..276651450
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml
new file mode 100644
index 000000000..922003b84
--- /dev/null
+++ b/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..0f4165692
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..61ae4fa02
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+# nRF Toolbox
+
+The nRF Toolbox is a container app that stores your Nordic Semiconductor apps for Bluetooth Smart in one location.
+
+It contains applications demonstrating Bluetooth Smart profiles:
+* **Cycling Speed and Cadence**,
+* **Running Speed and Cadence**,
+* **Heart Rate Monitor**,
+* **Blood Pressure Monitor**,
+* **Health Thermometer Monitor**,
+* **Glucose Monitor**,
+* **Proximity Monitor**.
+
+Since version 1.10.0 the *nRF Toolbox* supports also the **Nordic UART Service** which may be used for bidirectional text communication between devices. The UI allows one to create configurable remote control with UART interface.
+
+### Device Firmware Update
+
+The **Device Firmware Update (DFU)** profile allows one to update the application, bootloader and/or the Soft Device image over-the-air (OTA). It is compatible with Nordic Semiconductor nRF51822 devices that have the S110 SoftDevice and bootloader enabled. Since the 1.11.0 version nRF Toolbox allows to send the required init packet. More information about the init packet may be found here: [init packet handling](https://github.com/NordicSemiconductor/nRF-Master-Control-Panel/tree/master/init%20packet%20handling).
+
+The DFU has the following features:
+- Scans for devices that are in DFU mode.
+- Connects to devices in DFU mode and uploads the selected firmware (soft device, bootloader and/or application).
+- Allows HEX or BIN file upload through your phone or tablet.
+- Allows to update a soft device and bootloader from ZIP in one connection.
+- Pause, resume, and cancel file uploads.
+- Works in portrait and landscape orientation.
+- Includes pre-installed examples that consist of the Bluetooth Smart heart rate service and running speed and cadence service.
+
+### Dependencies
+
+In order to compile the project the **DFU Library is required**. This project may be found here: https://github.com/NordicSemiconductor/Android-DFU-Library.
+Please clone the nRF Toolbox and the DFU Library to the same root folder. The dependency is already configured in the gradle and set to *..:DFULibrary:dfu* module.
+
+The nRF Toolbox uses also the nRF Logger API library, that may be foud here: https://github.com/NordicSemiconductor/nRF-Logger-API. The library (jar file) and is located in the *libs* folder and a jar with its source code in the *source* folder in the *app* module. This library allows the app to create log entries in the [nRF Logger](https://play.google.com/store/apps/details?id=no.nordicsemi.android.log) application. Please, read the library documentation on GitHub for more information anout usage and permission.
+
+The graph in HRM profile is created using the [AChartEngine v1.1.0](http://www.achartengine.org) contributed based on the [Apache 2.0 license](http://www.apache.org/licenses/LICENSE-2.0).
+
+### Note
+- Android 4.3 or newer is required.
+- Tested on Nexus 4, Nexus 7, Samsung S3 and S4 with Android 4.3 and on Nexus 4, Nexus 5, Nexus 7, Nexus 9 with Android 4.4.4 and 5.
+- Compatible with nRF51822 devices that have S110 v5.2.1+ and the bootloader from nRF51 SDK v4.4.1+
+- nRF51822 Development kits can be ordered from http://www.nordicsemi.com/eng/Buy-Online.
+- The nRF51 SDK and S110 SoftDevice are available online at http://www.nordicsemi.com for developers who have purchased an nRF51822 product.
+
+### Known problems
+- Nexus 4 and Nexus 7 with Android 4.3 does not allow to unbound devices.
+- Reconnection to bondable devices may not work on several tested phones.
+- Nexus 4, 5 and 7 with Android 4.4 fails when reconnecting when Gatt Server is running.
+- Reset of Bluetooth adapter may be required if other error appear.
+
+### Know problems with DFU settings:
+- Setting Package Receipt Notification to OFF or less than ~400 will not work on some phones, f.e. Nexus 4, Nexus 7. On Nexus 5 with Android 4.4.4 it increases upload speed to 18kb/4.3 sec.
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/app.iml b/app/app.iml
new file mode 100644
index 000000000..9ec727f78
--- /dev/null
+++ b/app/app.iml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 000000000..2e44289b6
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.application'
+
+android {
+ signingConfigs {
+ }
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+ defaultConfig {
+ applicationId "no.nordicsemi.android.nrftoolbox"
+ minSdkVersion 18
+ targetSdkVersion 21
+ versionCode 27
+ versionName "1.11.4"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:21.0.3'
+ compile project(':..:DFULibrary:dfu')
+ compile files('libs/achartengine-1.1.0.jar')
+ compile files('libs/nrf-logger-v2.0.jar')
+}
diff --git a/app/libs/achartengine-1.1.0.jar b/app/libs/achartengine-1.1.0.jar
new file mode 100644
index 000000000..932031867
Binary files /dev/null and b/app/libs/achartengine-1.1.0.jar differ
diff --git a/app/libs/nrf-logger-v2.0.jar b/app/libs/nrf-logger-v2.0.jar
new file mode 100644
index 000000000..8d37a964b
Binary files /dev/null and b/app/libs/nrf-logger-v2.0.jar differ
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 000000000..49484f772
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\alno\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/app/sources/nrf-logger-v2.0-source.jar b/app/sources/nrf-logger-v2.0-source.jar
new file mode 100644
index 000000000..e25ca9095
Binary files /dev/null and b/app/sources/nrf-logger-v2.0-source.jar differ
diff --git a/app/src/androidTest/java/no/nordicsemi/android/nrftoolbox/ApplicationTest.java b/app/src/androidTest/java/no/nordicsemi/android/nrftoolbox/ApplicationTest.java
new file mode 100644
index 000000000..5e6a0ca98
--- /dev/null
+++ b/app/src/androidTest/java/no/nordicsemi/android/nrftoolbox/ApplicationTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..e7146ead4
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/fonts/trebuc.ttf b/app/src/main/assets/fonts/trebuc.ttf
new file mode 100644
index 000000000..84891986b
Binary files /dev/null and b/app/src/main/assets/fonts/trebuc.ttf differ
diff --git a/app/src/main/assets/fonts/trebucbd.ttf b/app/src/main/assets/fonts/trebucbd.ttf
new file mode 100644
index 000000000..663946d20
Binary files /dev/null and b/app/src/main/assets/fonts/trebucbd.ttf differ
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/AppHelpFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/AppHelpFragment.java
new file mode 100644
index 000000000..174eed8a7
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/AppHelpFragment.java
@@ -0,0 +1,73 @@
+/*
+ * 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.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+
+public class AppHelpFragment extends DialogFragment {
+ private static final String ARG_TEXT = "ARG_TEXT";
+ private static final String ARG_VERSION = "ARG_VERSION";
+
+ public static AppHelpFragment getInstance(final int aboutResId, final boolean appendVersion) {
+ final AppHelpFragment fragment = new AppHelpFragment();
+
+ final Bundle args = new Bundle();
+ args.putInt(ARG_TEXT, aboutResId);
+ args.putBoolean(ARG_VERSION, appendVersion);
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ public static AppHelpFragment getInstance(final int aboutResId) {
+ final AppHelpFragment fragment = new AppHelpFragment();
+
+ final Bundle args = new Bundle();
+ args.putInt(ARG_TEXT, aboutResId);
+ args.putBoolean(ARG_VERSION, false);
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final Bundle args = getArguments();
+ final StringBuilder text = new StringBuilder(getString(args.getInt(ARG_TEXT)));
+
+ final boolean appendVersion = args.getBoolean(ARG_VERSION);
+ if (appendVersion) {
+ try {
+ final String version = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0).versionName;
+ text.append(getString(R.string.about_version, version));
+ } catch (final NameNotFoundException e) {
+ // do nothing
+ }
+ }
+ return new AlertDialog.Builder(getActivity()).setTitle(R.string.about_title).setMessage(text)
+ .setPositiveButton(R.string.ok, null).create();
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/FeaturesActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/FeaturesActivity.java
new file mode 100644
index 000000000..d6a7389dd
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/FeaturesActivity.java
@@ -0,0 +1,190 @@
+/*
+ * 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 java.util.List;
+
+import no.nordicsemi.android.nrftoolbox.adapter.AppAdapter;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class FeaturesActivity extends ActionBarActivity {
+ private static final String UTILS_CATEGORY = "no.nordicsemi.android.nrftoolbox.UTILS";
+ private static final String MCP_PACKAGE = "no.nordicsemi.android.mcp";
+ private static final String MCP_CLASS = MCP_PACKAGE + ".DeviceListActivity";
+ private static final String MCP_MARKET_URI = "market://details?id=no.nordicsemi.android.mcp";
+
+ private DrawerLayout mDrawerLayout;
+ private ActionBarDrawerToggle mDrawerToggle;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_features);
+
+ // ensure that Bluetooth exists
+ if (!ensureBLEExists())
+ finish();
+
+ final DrawerLayout drawer = mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ drawer.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+
+ // Set the drawer toggle as the DrawerListener
+ mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close);
+ drawer.setDrawerListener(mDrawerToggle);
+
+ // setup plug-ins in the drawer
+ setupPluginsInDrawer((ViewGroup) drawer.findViewById(R.id.plugin_container));
+
+ // configure the app grid
+ final GridView grid = (GridView) findViewById(R.id.grid);
+ grid.setAdapter(new AppAdapter(this));
+ grid.setEmptyView(findViewById(android.R.id.empty));
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.help, menu);
+ return true;
+ }
+
+ @Override
+ protected void onPostCreate(final Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ mDrawerToggle.syncState();
+ }
+
+ @Override
+ public void onConfigurationChanged(final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ // Pass the event to ActionBarDrawerToggle, if it returns
+ // true, then it has handled the app icon touch event
+ if (mDrawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ switch (item.getItemId()) {
+ case R.id.action_about:
+ final AppHelpFragment fragment = AppHelpFragment.getInstance(R.string.about_text, true);
+ fragment.show(getFragmentManager(), null);
+ break;
+ }
+ return true;
+ }
+
+ private void setupPluginsInDrawer(final ViewGroup container) {
+ final LayoutInflater inflater = LayoutInflater.from(this);
+ final PackageManager pm = getPackageManager();
+
+ // look for Master Control Panel
+ final Intent mcpIntent = new Intent(Intent.ACTION_MAIN);
+ mcpIntent.setClassName(MCP_PACKAGE, MCP_CLASS);
+ final ResolveInfo mcpInfo = pm.resolveActivity(mcpIntent, 0);
+
+ // configure link to Master Control Panel
+ final TextView mcpItem = (TextView) container.findViewById(R.id.link_mcp);
+ if (mcpInfo == null) {
+ mcpItem.setTextColor(Color.GRAY);
+ ColorMatrix grayscale = new ColorMatrix();
+ grayscale.setSaturation(0.0f);
+ mcpItem.getCompoundDrawables()[0].setColorFilter(new ColorMatrixColorFilter(grayscale));
+ }
+ mcpItem.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ Intent action = mcpIntent;
+ if (mcpInfo == null)
+ action = new Intent(Intent.ACTION_VIEW, Uri.parse(MCP_MARKET_URI));
+ action.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ try {
+ startActivity(action);
+ } catch (final ActivityNotFoundException e) {
+ Toast.makeText(FeaturesActivity.this, R.string.no_application_play, Toast.LENGTH_SHORT).show();
+ }
+ mDrawerLayout.closeDrawers();
+ }
+ });
+
+ // look for other plug-ins
+ final Intent utilsIntent = new Intent(Intent.ACTION_MAIN);
+ utilsIntent.addCategory(UTILS_CATEGORY);
+
+ final List appList = pm.queryIntentActivities(utilsIntent, 0);
+ for (final ResolveInfo info : appList) {
+ final View item = inflater.inflate(R.layout.drawer_plugin, container, false);
+ final ImageView icon = (ImageView) item.findViewById(android.R.id.icon);
+ final TextView label = (TextView) item.findViewById(android.R.id.text1);
+
+ label.setText(info.loadLabel(pm));
+ icon.setImageDrawable(info.loadIcon(pm));
+ item.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ final Intent intent = new Intent();
+ intent.setComponent(new ComponentName(info.activityInfo.packageName, info.activityInfo.name));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(intent);
+ mDrawerLayout.closeDrawers();
+ }
+ });
+ container.addView(item);
+ }
+ }
+
+ private boolean ensureBLEExists() {
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Toast.makeText(this, R.string.no_ble, Toast.LENGTH_LONG).show();
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/SplashscreenActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/SplashscreenActivity.java
new file mode 100644
index 000000000..6e35d0244
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/SplashscreenActivity.java
@@ -0,0 +1,55 @@
+/*
+ * 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.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+
+public class SplashscreenActivity extends Activity {
+ /** Splash screen duration time in milliseconds */
+ private static final int DELAY = 1000;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_splashscreen);
+
+ // Jump to SensorsActivity after DELAY milliseconds
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ final Intent intent = new Intent(SplashscreenActivity.this, FeaturesActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(intent);
+ finish();
+ }
+ }, DELAY);
+ }
+
+ @Override
+ public void onBackPressed() {
+ // do nothing. Protect from exiting the application when splash screen is shown
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/adapter/AppAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/adapter/AppAdapter.java
new file mode 100644
index 000000000..e01978e90
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/adapter/AppAdapter.java
@@ -0,0 +1,122 @@
+/*
+ * 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.adapter;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class AppAdapter extends BaseAdapter {
+ private static final String CATEGORY = "no.nordicsemi.android.nrftoolbox.LAUNCHER";
+ private static final String MCP_PACKAGE = "no.nordicsemi.android.mcp";
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final LayoutInflater mInflater;
+ private final List mApplications;
+
+ public AppAdapter(final Context context) {
+ mContext = context;
+ mInflater = LayoutInflater.from(context);
+
+ // get nRF installed app plugins from package manager
+ final PackageManager pm = mPackageManager = context.getPackageManager();
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(CATEGORY);
+
+ final List appList = mApplications = pm.queryIntentActivities(intent, 0);
+ // TODO remove the following loop after some time, when there will be no more MCP 1.1 at the market.
+ for (final ResolveInfo info : appList) {
+ if (MCP_PACKAGE.equals(info.activityInfo.packageName)) {
+ appList.remove(info);
+ break;
+ }
+ }
+ Collections.sort(appList, new ResolveInfo.DisplayNameComparator(pm));
+ }
+
+ @Override
+ public int getCount() {
+ return mApplications.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mApplications.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = mInflater.inflate(R.layout.feature_icon, parent, false);
+
+ final ViewHolder holder = new ViewHolder();
+ holder.view = view;
+ holder.icon = (ImageView) view.findViewById(R.id.icon);
+ holder.label = (TextView) view.findViewById(R.id.label);
+ view.setTag(holder);
+ }
+
+ final ResolveInfo info = mApplications.get(position);
+ final PackageManager pm = mPackageManager;
+
+ final ViewHolder holder = (ViewHolder) view.getTag();
+ holder.icon.setImageDrawable(info.loadIcon(pm));
+ holder.label.setText(info.loadLabel(pm).toString().toUpperCase(Locale.US));
+ holder.view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Intent intent = new Intent();
+ intent.setComponent(new ComponentName(info.activityInfo.packageName, info.activityInfo.name));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ mContext.startActivity(intent);
+ }
+ });
+
+ return view;
+ }
+
+ private class ViewHolder {
+ private View view;
+ private ImageView icon;
+ private TextView label;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/app/ExpandableListActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/app/ExpandableListActivity.java
new file mode 100644
index 000000000..7d8638f9c
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/app/ExpandableListActivity.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package no.nordicsemi.android.nrftoolbox.app;
+
+import java.util.List;
+import java.util.Map;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import android.app.Activity;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.SimpleCursorTreeAdapter;
+import android.widget.SimpleExpandableListAdapter;
+
+/**
+ * An activity that displays an expandable list of items by binding to a data source implementing the ExpandableListAdapter, and exposes event handlers when the
+ * user selects an item.
+ *
+ * ExpandableListActivity hosts a {@link android.widget.ExpandableListView ExpandableListView} object that can be bound to different data sources that provide a
+ * two-levels of data (the top-level is group, and below each group are children). Binding, screen layout, and row layout are discussed in the following
+ * sections.
+ *
+ * Screen Layout
+ *
+ *
+ * ExpandableListActivity has a default layout that consists of a single, full-screen, centered expandable list. However, if you desire, you can customize the
+ * screen layout by setting your own view layout with setContentView() in onCreate(). To do this, your own view MUST contain an ExpandableListView object with
+ * the id "@android:id/list" (or {@link android.R.id#list} if it's in code)
+ *
+ * Optionally, your custom view can contain another view object of any type to display when the list view is empty. This "empty list" notifier must have an id
+ * "android:empty". Note that when an empty view is present, the expandable list view will be hidden when there is no data to display.
+ *
+ * The following code demonstrates an (ugly) custom screen layout. It has a list with a green background, and an alternate red "no data" message.
+ *
+ * The {@link ExpandableListAdapter} set in the {@link ExpandableListActivity} via {@link #setListAdapter(ExpandableListAdapter)} provides the {@link View}s for
+ * each row. This adapter has separate methods for providing the group {@link View}s and child {@link View}s. There are a couple provided
+ * {@link ExpandableListAdapter}s that simplify use of adapters: {@link SimpleCursorTreeAdapter} and {@link SimpleExpandableListAdapter}.
+ *
+ * With these, you can specify the layout of individual rows for groups and children in the list. These constructor takes a few parameters that specify layout
+ * resources for groups and children. It also has additional parameters that let you specify which data field to associate with which object in the row layout
+ * resource. The {@link SimpleCursorTreeAdapter} fetches data from {@link Cursor}s and the {@link SimpleExpandableListAdapter} fetches data from {@link List}s
+ * of {@link Map}s.
+ *
+ *
+ * Android provides some standard row layout resources. These are in the {@link android.R.layout} class, and have names such as simple_list_item_1,
+ * simple_list_item_2, and two_line_list_item. The following layout XML is the source for the resource two_line_list_item, which displays two data fields,one
+ * above the other, for each list row.
+ *
+ * You must identify the data bound to each TextView object in this layout. The syntax for this is discussed in the next section.
+ *
+ *
+ * Binding to Data
+ *
+ *
+ * You bind the ExpandableListActivity's ExpandableListView object to data using a class that implements the {@link android.widget.ExpandableListAdapter
+ * ExpandableListAdapter} interface. Android provides two standard list adapters: {@link android.widget.SimpleExpandableListAdapter SimpleExpandableListAdapter}
+ * for static data (Maps), and {@link android.widget.SimpleCursorTreeAdapter SimpleCursorTreeAdapter} for Cursor query results.
+ *
+ *
+ * @see #setListAdapter
+ * @see android.widget.ExpandableListView
+ */
+public class ExpandableListActivity extends ActionBarActivity implements
+ OnCreateContextMenuListener,
+ ExpandableListView.OnChildClickListener, ExpandableListView.OnGroupCollapseListener,
+ ExpandableListView.OnGroupExpandListener {
+ ExpandableListAdapter mAdapter;
+ ExpandableListView mList;
+ boolean mFinishedStart = false;
+
+ /**
+ * Override this to populate the context menu when an item is long pressed. menuInfo will contain an
+ * {@link android.widget.ExpandableListView.ExpandableListContextMenuInfo} whose packedPosition is a packed position that should be used with
+ * {@link ExpandableListView#getPackedPositionType(long)} and the other similar methods.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ }
+
+ /**
+ * Override this for receiving callbacks when a child has been clicked.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
+ int childPosition, long id) {
+ return false;
+ }
+
+ /**
+ * Override this for receiving callbacks when a group has been collapsed.
+ */
+ @Override
+ public void onGroupCollapse(int groupPosition) {
+ }
+
+ /**
+ * Override this for receiving callbacks when a group has been expanded.
+ */
+ @Override
+ public void onGroupExpand(int groupPosition) {
+ }
+
+ /**
+ * Ensures the expandable list view has been created before Activity restores all of the view states.
+ *
+ * @see Activity#onRestoreInstanceState(Bundle)
+ */
+ @Override
+ protected void onRestoreInstanceState(Bundle state) {
+ ensureList();
+ super.onRestoreInstanceState(state);
+ }
+
+ /**
+ * Updates the screen state (current list and other views) when the content changes.
+ *
+ * @see ActionBarActivity#onSupportContentChanged()
+ */
+ @Override
+ public void onSupportContentChanged() {
+ super.onContentChanged();
+ View emptyView = findViewById(R.id.empty);
+ mList = (ExpandableListView) findViewById(R.id.list);
+ if (mList == null) {
+ throw new RuntimeException(
+ "Your content must have a ExpandableListView whose id attribute is " +
+ "'R.id.list'");
+ }
+ if (emptyView != null) {
+ mList.setEmptyView(emptyView);
+ }
+ mList.setOnChildClickListener(this);
+ mList.setOnGroupExpandListener(this);
+ mList.setOnGroupCollapseListener(this);
+
+ if (mFinishedStart) {
+ setListAdapter(mAdapter);
+ }
+ mFinishedStart = true;
+ }
+
+ /**
+ * Provide the adapter for the expandable list.
+ */
+ public void setListAdapter(ExpandableListAdapter adapter) {
+ synchronized (this) {
+ ensureList();
+ mAdapter = adapter;
+ mList.setAdapter(adapter);
+ }
+ }
+
+ /**
+ * Get the activity's expandable list view widget. This can be used to get the selection, set the selection, and many other useful functions.
+ *
+ * @see ExpandableListView
+ */
+ public ExpandableListView getExpandableListView() {
+ ensureList();
+ return mList;
+ }
+
+ /**
+ * Get the ExpandableListAdapter associated with this activity's ExpandableListView.
+ */
+ public ExpandableListAdapter getExpandableListAdapter() {
+ return mAdapter;
+ }
+
+ private void ensureList() {
+ if (mList != null) {
+ return;
+ }
+ setContentView(R.layout.expandable_list_content);
+ }
+
+ /**
+ * Gets the ID of the currently selected group or child.
+ *
+ * @return The ID of the currently selected group or child.
+ */
+ public long getSelectedId() {
+ return mList.getSelectedId();
+ }
+
+ /**
+ * Gets the position (in packed position representation) of the currently selected group or child. Use {@link ExpandableListView#getPackedPositionType},
+ * {@link ExpandableListView#getPackedPositionGroup}, and {@link ExpandableListView#getPackedPositionChild} to unpack the returned packed position.
+ *
+ * @return A packed position representation containing the currently selected group or child's position and type.
+ */
+ public long getSelectedPosition() {
+ return mList.getSelectedPosition();
+ }
+
+ /**
+ * Sets the selection to the specified child. If the child is in a collapsed group, the group will only be expanded and child subsequently selected if
+ * shouldExpandGroup is set to true, otherwise the method will return false.
+ *
+ * @param groupPosition
+ * The position of the group that contains the child.
+ * @param childPosition
+ * The position of the child within the group.
+ * @param shouldExpandGroup
+ * Whether the child's group should be expanded if it is collapsed.
+ * @return Whether the selection was successfully set on the child.
+ */
+ public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) {
+ return mList.setSelectedChild(groupPosition, childPosition, shouldExpandGroup);
+ }
+
+ /**
+ * Sets the selection to the specified group.
+ *
+ * @param groupPosition
+ * The position of the group that should be selected.
+ */
+ public void setSelectedGroup(int groupPosition) {
+ mList.setSelectedGroup(groupPosition);
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMActivity.java
new file mode 100644
index 000000000..67970cef1
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMActivity.java
@@ -0,0 +1,169 @@
+/*
+ * 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.bpm;
+
+import java.util.Calendar;
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileActivity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class BPMActivity extends BleProfileActivity implements BPMManagerCallbacks {
+ @SuppressWarnings("unused")
+ private static final String TAG = "BPMActivity";
+
+ private TextView mSystolicView;
+ private TextView mSystolicUnitView;
+ private TextView mDiastolicView;
+ private TextView mDiastolicUnitView;
+ private TextView mMeanAPView;
+ private TextView mMeanAPUnitView;
+ private TextView mPulseView;
+ private TextView mTimestampView;
+
+ @Override
+ protected void onCreateView(Bundle savedInstanceState) {
+ setContentView(R.layout.activity_feature_bpm);
+ setGUI();
+ }
+
+ private void setGUI() {
+ mSystolicView = (TextView) findViewById(R.id.systolic);
+ mSystolicUnitView = (TextView) findViewById(R.id.systolic_unit);
+ mDiastolicView = (TextView) findViewById(R.id.diastolic);
+ mDiastolicUnitView = (TextView) findViewById(R.id.diastolic_unit);
+ mMeanAPView = (TextView) findViewById(R.id.mean_ap);
+ mMeanAPUnitView = (TextView) findViewById(R.id.mean_ap_unit);
+ mPulseView = (TextView) findViewById(R.id.pulse);
+ mTimestampView = (TextView) findViewById(R.id.timestamp);
+ }
+
+ @Override
+ protected int getDefaultDeviceName() {
+ return R.string.bpm_default_name;
+ }
+
+ @Override
+ protected int getAboutTextId() {
+ return R.string.bpm_about_text;
+ }
+
+ @Override
+ protected UUID getFilterUUID() {
+ return BPMManager.BP_SERVICE_UUID;
+ }
+
+ @Override
+ protected BleManager initializeManager() {
+ final BPMManager manager = BPMManager.getBPMManager();
+ manager.setGattCallbacks(this);
+ return manager;
+ }
+
+ @Override
+ protected void setDefaultUI() {
+ mSystolicView.setText(R.string.not_available_value);
+ mSystolicUnitView.setText(null);
+ mDiastolicView.setText(R.string.not_available_value);
+ mDiastolicUnitView.setText(null);
+ mMeanAPView.setText(R.string.not_available_value);
+ mMeanAPUnitView.setText(null);
+ mPulseView.setText(R.string.not_available_value);
+ mTimestampView.setText(R.string.not_available);
+ }
+
+ @Override
+ public void onServicesDiscovered(final boolean optionalServicesFound) {
+ // this may notify user or show some views
+ }
+
+ @Override
+ public void onBloodPressureMeasurementIndicationsEnabled() {
+ // this may notify user
+ }
+
+ @Override
+ public void onIntermediateCuffPressureNotificationEnabled() {
+ // this may notify user
+ }
+
+ @Override
+ public void onBloodPressureMeasurementRead(final float systolic, final float diastolic, final float meanArterialPressure, final int unit) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mSystolicView.setText(Float.toString(systolic));
+ mDiastolicView.setText(Float.toString(diastolic));
+ mMeanAPView.setText(Float.toString(meanArterialPressure));
+
+ mSystolicUnitView.setText(unit == UNIT_mmHG ? R.string.bpm_unit_mmhg : R.string.bpm_unit_kpa);
+ mDiastolicUnitView.setText(unit == UNIT_mmHG ? R.string.bpm_unit_mmhg : R.string.bpm_unit_kpa);
+ mMeanAPUnitView.setText(unit == UNIT_mmHG ? R.string.bpm_unit_mmhg : R.string.bpm_unit_kpa);
+ }
+ });
+ }
+
+ @Override
+ public void onIntermediateCuffPressureRead(final float cuffPressure, final int unit) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mSystolicView.setText(Float.toString(cuffPressure));
+ mDiastolicView.setText(R.string.not_available_value);
+ mMeanAPView.setText(R.string.not_available_value);
+
+ mSystolicUnitView.setText(unit == UNIT_mmHG ? R.string.bpm_unit_mmhg : R.string.bpm_unit_kpa);
+ mDiastolicUnitView.setText(null);
+ mMeanAPUnitView.setText(null);
+ }
+ });
+ }
+
+ @Override
+ public void onPulseRateRead(final float pulseRate) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (pulseRate >= 0)
+ mPulseView.setText(Float.toString(pulseRate));
+ else
+ mPulseView.setText(R.string.not_available_value);
+ }
+ });
+ }
+
+ @Override
+ public void onTimestampRead(final Calendar calendar) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (calendar != null)
+ mTimestampView.setText(getString(R.string.bpm_timestamp, calendar));
+ else
+ mTimestampView.setText(R.string.not_available);
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManager.java
new file mode 100644
index 000000000..717ffca13
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManager.java
@@ -0,0 +1,316 @@
+/*
+ * 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.bpm;
+
+import java.util.Calendar;
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+public class BPMManager implements BleManager {
+ private final String TAG = "BPMManager";
+
+ private BPMManagerCallbacks mCallbacks;
+ private BluetoothGatt mBluetoothGatt;
+ private Context mContext;
+
+ public final static UUID BP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb");
+ public final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb");
+ /** Blood Pressure Measurement characteristic */
+ private static final UUID BPM_CHARACTERISTIC_UUID = UUID.fromString("00002A35-0000-1000-8000-00805f9b34fb");
+ /** Intermediate Cuff Pressure characteristic */
+ private static final UUID ICP_CHARACTERISTIC_UUID = UUID.fromString("00002A36-0000-1000-8000-00805f9b34fb");
+ /** Battery Level characteristic */
+ private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb");
+ /** Client configuration descriptor that will allow us to enable notifications and indications */
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+ private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
+ private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
+ private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor";
+ private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information";
+
+ private BluetoothGattCharacteristic mBPMCharacteristic, mICPCharacteristic, mBatteryCharacteristic;
+
+ private static BPMManager managerInstance = null;
+
+ /**
+ * Returns the singleton implementation of BPMManager
+ */
+ public static synchronized BPMManager getBPMManager() {
+ if (managerInstance == null) {
+ managerInstance = new BPMManager();
+ }
+ return managerInstance;
+ }
+
+ /**
+ * Callbacks for activity {@link BPMActivity} that implements {@link BPMManagerCallbacks} interface activity use this method to register itself for receiving callbacks
+ */
+ @Override
+ public void setGattCallbacks(final BPMManagerCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void connect(final Context context, final BluetoothDevice device) {
+ mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
+ mContext = context;
+ }
+
+ @Override
+ public void disconnect() {
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ }
+ }
+
+ private final BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+ final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
+
+ DebugLogger.d(TAG, "Bond state changed for: " + device.getAddress() + " new state: " + bondState + " previous: " + previousBondState);
+
+ // skip other devices
+ if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
+ return;
+
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ // We've read Battery Level, now let'so enable ICP notifications or BPM indications
+ if (mICPCharacteristic != null)
+ enableIntermediateCuffPressureNotification(mBluetoothGatt);
+ else
+ enableBloodPressureMeasurementIndication(mBluetoothGatt);
+
+ mContext.unregisterReceiver(this);
+ mCallbacks.onBonded();
+ }
+ }
+ };
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc
+ */
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ mCallbacks.onDeviceConnected();
+ // start discovering services
+ gatt.discoverServices();
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ mCallbacks.onDeviceDisconnected();
+ gatt.close();
+ }
+ } else {
+ mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ for (BluetoothGattService service : gatt.getServices()) {
+ if (BP_SERVICE_UUID.equals(service.getUuid())) {
+ mBPMCharacteristic = service.getCharacteristic(BPM_CHARACTERISTIC_UUID);
+ mICPCharacteristic = service.getCharacteristic(ICP_CHARACTERISTIC_UUID);
+ } else if (BATTERY_SERVICE.equals(service.getUuid())) {
+ mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC);
+ }
+ }
+ // Validate the device
+ if (mBPMCharacteristic == null) {
+ mCallbacks.onDeviceNotSupported();
+ gatt.disconnect();
+ return;
+ }
+ mCallbacks.onServicesDiscovered(mICPCharacteristic != null);
+
+ // We have discovered services, let's start notifications and indications, one by one: battery, icp (if exists), bpm
+ if (mBatteryCharacteristic != null) {
+ readBatteryLevel(gatt);
+ } else if (mICPCharacteristic != null) {
+ enableIntermediateCuffPressureNotification(gatt);
+ } else {
+ enableBloodPressureMeasurementIndication(gatt);
+ }
+ } else {
+ DebugLogger.e(TAG, "onServicesDiscovered error " + status);
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (BATTERY_LEVEL_CHARACTERISTIC.equals(characteristic.getUuid())) {
+ final int batteryValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+ mCallbacks.onBatteryValueReceived(batteryValue);
+
+ // We've read Battery Level, now let'so enable ICP notifications or BPM indications
+ if (mICPCharacteristic != null)
+ enableIntermediateCuffPressureNotification(gatt);
+ else
+ enableBloodPressureMeasurementIndication(gatt);
+ }
+ } else {
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
+ // ICP or BPM characteristic returned value
+
+ // first byte - flags
+ int offset = 0;
+ final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset++);
+ // See BPMManagerCallbacks.UNIT_* for unit options
+ final int unit = flags & 0x01;
+ final boolean timestampPresent = (flags & 0x02) > 0;
+ final boolean pulseRatePresent = (flags & 0x04) > 0;
+
+ if (BPM_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) {
+ // following bytes - systolic, diastolic and mean arterial pressure
+ final float systolic = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
+ final float diastolic = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 2);
+ final float meanArterialPressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 4);
+ offset += 6;
+ mCallbacks.onBloodPressureMeasurementRead(systolic, diastolic, meanArterialPressure, unit);
+ } else if (ICP_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) {
+ // following bytes - cuff pressure. Diastolic and MAP are unused
+ final float cuffPressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
+ offset += 6;
+ mCallbacks.onIntermediateCuffPressureRead(cuffPressure, unit);
+ }
+
+ // parse timestamp if present
+ if (timestampPresent) {
+ final Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.YEAR, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset));
+ calendar.set(Calendar.MONTH, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2) + 1); // months are 1-based
+ calendar.set(Calendar.DAY_OF_MONTH, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 3));
+ calendar.set(Calendar.HOUR_OF_DAY, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 4));
+ calendar.set(Calendar.MINUTE, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 5));
+ calendar.set(Calendar.SECOND, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 6));
+ offset += 7;
+ mCallbacks.onTimestampRead(calendar);
+ } else
+ mCallbacks.onTimestampRead(null);
+
+ // parse pulse rate if present
+ if (pulseRatePresent) {
+ final float pulseRate = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
+ offset += 2;
+ mCallbacks.onPulseRateRead(pulseRate);
+ } else
+ mCallbacks.onPulseRateRead(-1.0f);
+ }
+
+ @Override
+ public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (BPM_CHARACTERISTIC_UUID.equals(descriptor.getCharacteristic().getUuid()))
+ mCallbacks.onBloodPressureMeasurementIndicationsEnabled();
+
+ if (ICP_CHARACTERISTIC_UUID.equals(descriptor.getCharacteristic().getUuid())) {
+ mCallbacks.onIntermediateCuffPressureNotificationEnabled();
+ enableBloodPressureMeasurementIndication(gatt);
+ }
+ } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+ if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
+ mCallbacks.onBondingRequired();
+
+ final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ mContext.registerReceiver(mBondingBroadcastReceiver, filter);
+ } else {
+ DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
+ mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
+ }
+ } else {
+ mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status);
+ }
+ }
+
+ /**
+ * Reads battery level on the device
+ */
+ private void readBatteryLevel(final BluetoothGatt gatt) {
+ DebugLogger.d(TAG, "readBatteryLevel()");
+ gatt.readCharacteristic(mBatteryCharacteristic);
+ }
+ };
+
+ /**
+ * Enabling notification on Intermediate Cuff Pressure Characteristic
+ */
+ private void enableIntermediateCuffPressureNotification(final BluetoothGatt gatt) {
+ DebugLogger.d(TAG, "enableIntermediateCuffPressureNotification()");
+ gatt.setCharacteristicNotification(mICPCharacteristic, true);
+ final BluetoothGattDescriptor descriptor = mICPCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ }
+
+ /**
+ * Enabling indications on Blood Pressure Measurement Characteristic
+ */
+ private void enableBloodPressureMeasurementIndication(final BluetoothGatt gatt) {
+ DebugLogger.d(TAG, "enableBloodPressureMeasurementIndication()");
+ gatt.setCharacteristicNotification(mBPMCharacteristic, true);
+ final BluetoothGattDescriptor descriptor = mBPMCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ }
+
+ @Override
+ public void closeBluetoothGatt() {
+ try {
+ mContext.unregisterReceiver(mBondingBroadcastReceiver);
+ } catch (Exception e) {
+ // the receiver must have been not registered or unregistered before
+ }
+
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBPMCharacteristic = null;
+ mBatteryCharacteristic = null;
+ mBluetoothGatt = null;
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManagerCallbacks.java
new file mode 100644
index 000000000..e08d41bc0
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManagerCallbacks.java
@@ -0,0 +1,77 @@
+/*
+ * 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.bpm;
+
+import java.util.Calendar;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
+
+public interface BPMManagerCallbacks extends BleManagerCallbacks {
+ public static final int UNIT_mmHG = 0;
+ public static final int UNIT_kPa = 1;
+
+ /**
+ * Called when the Blood Pressure Measurement characteristic indication has been enabled
+ */
+ public void onBloodPressureMeasurementIndicationsEnabled();
+
+ /**
+ * Called when the Intermediate Cuff Pressure characteristic notification has been enabled
+ */
+ public void onIntermediateCuffPressureNotificationEnabled();
+
+ /**
+ * Called when new BPM value has been obtained from the sensor
+ *
+ * @param systolic
+ * @param diastolic
+ * @param meanArterialPressure
+ * @param unit
+ * one of the following {@link #UNIT_kPa} or {@link #UNIT_mmHG}
+ */
+ public void onBloodPressureMeasurementRead(final float systolic, final float diastolic, final float meanArterialPressure, final int unit);
+
+ /**
+ * Called when new ICP value has been obtained from the device
+ *
+ * @param cuffPressure
+ * @param unit
+ * one of the following {@link #UNIT_kPa} or {@link #UNIT_mmHG}
+ */
+ public void onIntermediateCuffPressureRead(final float cuffPressure, final int unit);
+
+ /**
+ * Called when new pulse rate value has been obtained from the device. If there was no pulse rate in the packet the parameter will be equal -1.0f
+ *
+ * @param pulseRate
+ * pulse rate or -1.0f
+ */
+ public void onPulseRateRead(final float pulseRate);
+
+ /**
+ * Called when the timestamp value has been read from the device. If there was no timestamp information the parameter will be null
+ *
+ * @param calendar
+ * the timestamp or null
+ */
+ public void onTimestampRead(final Calendar calendar);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCActivity.java
new file mode 100644
index 000000000..170f96d54
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCActivity.java
@@ -0,0 +1,185 @@
+/*
+ * 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.csc;
+
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsActivity;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.Menu;
+import android.widget.TextView;
+
+public class CSCActivity extends BleProfileServiceReadyActivity {
+ private TextView mSpeedView;
+ private TextView mCadenceView;
+ private TextView mDistanceView;
+ private TextView mDistanceUnitView;
+ private TextView mTotalDistanceView;
+ private TextView mGearRatioView;
+
+ @Override
+ protected void onCreateView(final Bundle savedInstanceState) {
+ setContentView(R.layout.activity_feature_csc);
+ setGui();
+ }
+
+ @Override
+ protected void onInitialize(final Bundle savedInstanceState) {
+ LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, makeIntentFilter());
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);
+ }
+
+ private void setGui() {
+ mSpeedView = (TextView) findViewById(R.id.speed);
+ mCadenceView = (TextView) findViewById(R.id.cadence);
+ mDistanceView = (TextView) findViewById(R.id.distance);
+ mDistanceUnitView = (TextView) findViewById(R.id.distance_unit);
+ mTotalDistanceView = (TextView) findViewById(R.id.distance_total);
+ mGearRatioView = (TextView) findViewById(R.id.ratio);
+ }
+
+ @Override
+ protected void setDefaultUI() {
+ mSpeedView.setText(R.string.not_available_value);
+ mCadenceView.setText(R.string.not_available_value);
+ mDistanceView.setText(R.string.not_available_value);
+ mDistanceUnitView.setText(R.string.csc_distance_unit_m);
+ mTotalDistanceView.setText(R.string.not_available_value);
+ mGearRatioView.setText(R.string.not_available_value);
+ }
+
+ @Override
+ protected int getLoggerProfileTitle() {
+ return R.string.csc_feature_title;
+ }
+
+ @Override
+ protected int getDefaultDeviceName() {
+ return R.string.csc_default_name;
+ }
+
+ @Override
+ protected int getAboutTextId() {
+ return R.string.csc_about_text;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.csc_menu, menu);
+ return true;
+ }
+
+ @Override
+ protected boolean onOptionsItemSelected(final int itemId) {
+ switch (itemId) {
+ case R.id.action_settings:
+ final Intent intent = new Intent(this, SettingsActivity.class);
+ startActivity(intent);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ protected Class extends BleProfileService> getServiceClass() {
+ return CSCService.class;
+ }
+
+ @Override
+ protected UUID getFilterUUID() {
+ return CSCManager.CYCLING_SPEED_AND_CADENCE_SERVICE_UUID;
+ }
+
+ @Override
+ protected void onServiceBinded(final CSCService.CSCBinder binder) {
+ // not used
+ }
+
+ @Override
+ protected void onServiceUnbinded() {
+ // not used
+ }
+
+ @Override
+ public void onServicesDiscovered(final boolean optionalServicesFound) {
+ // not used
+ }
+
+ private void onMeasurementReceived(final float speed, final float distance, final float totalDistance) {
+ mSpeedView.setText(String.format("%.1f", speed));
+ if (distance < 1000) { // 1 km in m
+ mDistanceView.setText(String.format("%.0f", distance));
+ mDistanceUnitView.setText(R.string.csc_distance_unit_m);
+ } else {
+ mDistanceView.setText(String.format("%.2f", distance / 1000.0f));
+ mDistanceUnitView.setText(R.string.csc_distance_unit_km);
+ }
+
+ mTotalDistanceView.setText(String.format("%.2f", totalDistance / 1000.0f));
+ }
+
+ private void onGearRatioUpdate(final float ratio, final int cadence) {
+ mGearRatioView.setText(String.format("%.1f", ratio));
+ mCadenceView.setText(String.format("%d", cadence));
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final String action = intent.getAction();
+
+ if (CSCService.BROADCAST_WHEEL_DATA.equals(action)) {
+ final float speed = intent.getFloatExtra(CSCService.EXTRA_SPEED, 0.0f);
+ final float distance = intent.getFloatExtra(CSCService.EXTRA_DISTANCE, CSCManagerCallbacks.NOT_AVAILABLE);
+ final float totalDistance = intent.getFloatExtra(CSCService.EXTRA_TOTAL_DISTANCE, CSCManagerCallbacks.NOT_AVAILABLE);
+ // Update GUI
+ onMeasurementReceived(speed, distance, totalDistance);
+ } else if (CSCService.BROADCAST_CRANK_DATA.equals(action)) {
+ final float ratio = intent.getFloatExtra(CSCService.EXTRA_GEAR_RATIO, 0);
+ final int cadence = intent.getIntExtra(CSCService.EXTRA_CADENCE, 0);
+ // Update GUI
+ onGearRatioUpdate(ratio, cadence);
+ }
+ }
+ };
+
+ private static IntentFilter makeIntentFilter() {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(CSCService.BROADCAST_WHEEL_DATA);
+ intentFilter.addAction(CSCService.BROADCAST_CRANK_DATA);
+ return intentFilter;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManager.java
new file mode 100644
index 000000000..d685d0972
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManager.java
@@ -0,0 +1,321 @@
+/*
+ * 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.csc;
+
+import java.util.List;
+import java.util.UUID;
+
+import no.nordicsemi.android.log.ILogSession;
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+public class CSCManager implements BleManager {
+ private static final String TAG = "RSCManager";
+
+ private CSCManagerCallbacks mCallbacks;
+ private BluetoothGatt mBluetoothGatt;
+ private Context mContext;
+ private ILogSession mLogSession;
+
+ private static final byte WHEEL_REVOLUTIONS_DATA_PRESENT = 0x01; // 1 bit
+ private static final byte CRANK_REVOLUTION_DATA_PRESENT = 0x02; // 1 bit
+
+ public final static UUID CYCLING_SPEED_AND_CADENCE_SERVICE_UUID = UUID.fromString("00001816-0000-1000-8000-00805f9b34fb");
+ /** Cycling Speed and Cadence Measurement characteristic */
+ private static final UUID CSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A5B-0000-1000-8000-00805f9b34fb");
+
+ private final static UUID BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb");
+ /** Battery Level characteristic */
+ private final static UUID BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb");
+ /** Client configuration descriptor that will allow us to enable notifications and indications */
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+ private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
+ private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
+ private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information";
+ private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor";
+ private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic";
+
+ private BluetoothGattCharacteristic mCSCMeasurementCharacteristic, mBatteryCharacteristic;
+ private boolean mBatteryLevelNotificationsEnabled;
+
+ public CSCManager(final Context context) {
+ // Register bonding broadcast receiver
+ final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ context.registerReceiver(mBondingBroadcastReceiver, filter);
+ }
+
+ @Override
+ public void setGattCallbacks(final CSCManagerCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ public void setLogger(final ILogSession session) {
+ mLogSession = session;
+ }
+
+ @Override
+ public void connect(final Context context, final BluetoothDevice device) {
+ mContext = context;
+
+ Logger.i(mLogSession, "[CSC] Gatt server started");
+ if (mBluetoothGatt == null) {
+ mBluetoothGatt = device.connectGatt(mContext, true, mGattCallback);
+ } else {
+ mBluetoothGatt.connect();
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ }
+ }
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
+ */
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ DebugLogger.d(TAG, "Device connected");
+ mBluetoothGatt.discoverServices();
+ //This will send callback to RSCActivity when device get connected
+ mCallbacks.onDeviceConnected();
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ DebugLogger.d(TAG, "Device disconnected");
+
+ // TODO It should check whether the user has requested disconnection or was it link loss. On Samsung S4 the DevKit reconnects itself just after linkloss but the Service is already dead.
+ mCallbacks.onDeviceDisconnected();
+ closeBluetoothGatt();
+ }
+ } else {
+ mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ final List services = gatt.getServices();
+ for (BluetoothGattService service : services) {
+ if (service.getUuid().equals(CYCLING_SPEED_AND_CADENCE_SERVICE_UUID)) {
+ DebugLogger.d(TAG, "Cycling Speed and Cadence service is found");
+ mCSCMeasurementCharacteristic = service.getCharacteristic(CSC_MEASUREMENT_CHARACTERISTIC_UUID);
+ } else if (service.getUuid().equals(BATTERY_SERVICE_UUID)) {
+ DebugLogger.d(TAG, "Battery service is found");
+ mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID);
+ }
+ }
+ if (mCSCMeasurementCharacteristic == null) {
+ mCallbacks.onDeviceNotSupported();
+ gatt.disconnect();
+ } else {
+ mCallbacks.onServicesDiscovered(false /* more characteristics not supported */);
+
+ // We have discovered services, let's start notifications and indications, one by one: battery, csc measurement
+ if (mBatteryCharacteristic != null) {
+ // Some devices has Battery Level characteristic without READ property
+ if ((mBatteryCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0)
+ readBatteryLevel(gatt);
+ else if ((mBatteryCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0)
+ enableBatteryLevelNotification(gatt);
+ else
+ enableCSCMeasurementNotification(gatt);
+ } else {
+ enableCSCMeasurementNotification(gatt);
+ }
+ }
+ } else {
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) {
+ int batteryValue = characteristic.getValue()[0];
+ mCallbacks.onBatteryValueReceived(batteryValue);
+
+ if (!mBatteryLevelNotificationsEnabled)
+ enableCSCMeasurementNotification(gatt);
+ }
+ } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+ if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
+ DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
+ mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
+ }
+ } else {
+ mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status);
+ }
+ }
+
+ @Override
+ public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (descriptor.getCharacteristic().getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) {
+ enableCSCMeasurementNotification(gatt);
+ }
+ } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+ if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
+ DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
+ mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
+ }
+ } else {
+ mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
+ // Decode the new data
+ int offset = 0;
+ final int flags = characteristic.getValue()[offset]; // 1 byte
+ offset += 1;
+
+ final boolean wheelRevPresent = (flags & WHEEL_REVOLUTIONS_DATA_PRESENT) > 0;
+ final boolean crankRevPreset = (flags & CRANK_REVOLUTION_DATA_PRESENT) > 0;
+
+ int wheelRevolutions = 0;
+ int lastWheelEventTime = 0;
+ if (wheelRevPresent) {
+ wheelRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset);
+ offset += 4;
+
+ lastWheelEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); // 1/1024 s
+ offset += 2;
+
+ // Notify listener about the new measurement
+ mCallbacks.onWheelMeasurementReceived(wheelRevolutions, lastWheelEventTime);
+ }
+
+ int crankRevolutions = 0;
+ int lastCrankEventTime = 0;
+ if (crankRevPreset) {
+ crankRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+ offset += 2;
+
+ lastCrankEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+ offset += 2;
+
+ // Notify listener about the new measurement
+ mCallbacks.onCrankMeasurementReceived(crankRevolutions, lastCrankEventTime);
+ }
+ }
+ };
+
+ private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+ final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
+
+ // skip other devices
+ if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
+ return;
+
+ DebugLogger.i(TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState);
+
+ if (bondState == BluetoothDevice.BOND_BONDING) {
+ mCallbacks.onBondingRequired();
+ return;
+ }
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ mCallbacks.onBonded();
+ }
+ }
+ };
+
+ public void readBatteryLevel() {
+ readBatteryLevel(mBluetoothGatt);
+ }
+
+ /**
+ * Reading the current value of the battery level
+ */
+ private void readBatteryLevel(final BluetoothGatt gatt) {
+ if (mBatteryCharacteristic != null) {
+ if ((mBatteryCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
+ DebugLogger.d(TAG, "reading battery characteristic");
+ gatt.readCharacteristic(mBatteryCharacteristic);
+ }
+ } else {
+ DebugLogger.w(TAG, "Battery Level Characteristic is null");
+ }
+ }
+
+ /**
+ * Enabling notification on Battery Level Characteristic
+ */
+ private void enableBatteryLevelNotification(final BluetoothGatt gatt) {
+ mBatteryLevelNotificationsEnabled = true;
+ gatt.setCharacteristicNotification(mBatteryCharacteristic, true);
+ final BluetoothGattDescriptor descriptor = mBatteryCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ }
+
+ /**
+ * Enabling notification on CSC Measurement Characteristic
+ */
+ private void enableCSCMeasurementNotification(final BluetoothGatt gatt) {
+ gatt.setCharacteristicNotification(mCSCMeasurementCharacteristic, true);
+ final BluetoothGattDescriptor descriptor = mCSCMeasurementCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ }
+
+ @Override
+ public void closeBluetoothGatt() {
+ try {
+ mContext.unregisterReceiver(mBondingBroadcastReceiver);
+ } catch (Exception e) {
+ // the receiver must have been not registered or unregistered before
+ }
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+ mBatteryLevelNotificationsEnabled = false;
+ mCallbacks = null;
+ mLogSession = null;
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManagerCallbacks.java
new file mode 100644
index 000000000..08a390002
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManagerCallbacks.java
@@ -0,0 +1,32 @@
+/*
+ * 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.csc;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
+
+public interface CSCManagerCallbacks extends BleManagerCallbacks {
+ public static final int NOT_AVAILABLE = -1;
+
+ public void onWheelMeasurementReceived(final int wheelRevolutions, final int wheelCrankEventTime);
+
+ public void onCrankMeasurementReceived(final int crankRevolutions, final int lastCrankEventTime);
+}
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
new file mode 100644
index 000000000..61921c758
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java
@@ -0,0 +1,252 @@
+/*
+ * 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.csc;
+
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsFragment;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class CSCService extends BleProfileService implements CSCManagerCallbacks {
+ private static final String TAG = "CSCService";
+
+ public static final String BROADCAST_WHEEL_DATA = "no.nordicsemi.android.nrftoolbox.csc.BROADCAST_WHEEL_DATA";
+ public static final String EXTRA_SPEED = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_SPEED";
+ /** Distance in meters */
+ public static final String EXTRA_DISTANCE = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_DISTANCE";
+ /** Total distance in meters */
+ public static final String EXTRA_TOTAL_DISTANCE = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_TOTAL_DISTANCE";
+
+ public static final String BROADCAST_CRANK_DATA = "no.nordicsemi.android.nrftoolbox.csc.BROADCAST_CRANK_DATA";
+ public static final String EXTRA_GEAR_RATIO = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_GEAR_RATIO";
+ public static final String EXTRA_CADENCE = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_CADENCE";
+
+ private static final String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.csc.ACTION_DISCONNECT";
+
+ private CSCManager mManager;
+ private boolean mBinded;
+
+ private int mFirstWheelRevolutions = -1;
+ private int mLastWheelRevolutions = -1;
+ private int mLastWheelEventTime = -1;
+ private float mWheelCadence = -1;
+ private int mLastCrankRevolutions = -1;
+ private int mLastCrankEventTime = -1;
+
+ private final static int NOTIFICATION_ID = 200;
+ private final static int OPEN_ACTIVITY_REQ = 0;
+ private final static int DISCONNECT_REQ = 1;
+
+ private final LocalBinder mBinder = new CSCBinder();
+
+ /**
+ * This local binder is an interface for the binded activity to operate with the RSC sensor
+ */
+ public class CSCBinder extends LocalBinder {
+ // empty
+ }
+
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
+
+ @Override
+ protected BleManager initializeManager() {
+ return mManager = new CSCManager(this);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DISCONNECT);
+ registerReceiver(mDisconnectActionBroadcastReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
+ cancelNotification();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
+
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(final Intent intent) {
+ mBinded = true;
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onRebind(final Intent intent) {
+ mBinded = true;
+ // when the activity rebinds to the service, remove the notification
+ cancelNotification();
+
+ // read the battery level when back in the Activity
+ if (isConnected())
+ mManager.readBatteryLevel();
+ }
+
+ @Override
+ public boolean onUnbind(final Intent intent) {
+ mBinded = false;
+ // when the activity closes we need to show the notification that user is connected to the sensor
+ createNotification(R.string.csc_notification_connected_message, 0);
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ protected void onServiceStarted() {
+ // logger is now available. Assign it to the manager
+ mManager.setLogger(getLogSession());
+ }
+
+ @Override
+ public void onWheelMeasurementReceived(final int wheelRevolutions, final int lastWheelEventTime) {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ final int circumference = Integer.parseInt(preferences.getString(SettingsFragment.SETTINGS_WHEEL_SIZE, String.valueOf(SettingsFragment.SETTINGS_WHEEL_SIZE_DEFAULT))); // [mm]
+
+ if (mFirstWheelRevolutions < 0)
+ mFirstWheelRevolutions = wheelRevolutions;
+
+ if (mLastWheelEventTime == lastWheelEventTime)
+ return;
+
+ if (mLastWheelRevolutions >= 0) {
+ float timeDifference = 0;
+ if (lastWheelEventTime < mLastWheelEventTime)
+ timeDifference = (65535 + lastWheelEventTime - mLastWheelEventTime) / 1024.0f; // [s]
+ else
+ timeDifference = (lastWheelEventTime - mLastWheelEventTime) / 1024.0f; // [s]
+ final float distanceDifference = (wheelRevolutions - mLastWheelRevolutions) * circumference / 1000.0f; // [m]
+ final float totalDistance = (float) wheelRevolutions * (float) circumference / 1000.0f; // [m]
+ final float distance = (float) (wheelRevolutions - mFirstWheelRevolutions) * (float) circumference / 1000.0f; // [m]
+ final float speed = distanceDifference / timeDifference;
+ mWheelCadence = (wheelRevolutions - mLastWheelRevolutions) * 60.0f / timeDifference;
+
+ final Intent broadcast = new Intent(BROADCAST_WHEEL_DATA);
+ broadcast.putExtra(EXTRA_SPEED, speed);
+ broadcast.putExtra(EXTRA_DISTANCE, distance);
+ broadcast.putExtra(EXTRA_TOTAL_DISTANCE, totalDistance);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+ mLastWheelRevolutions = wheelRevolutions;
+ mLastWheelEventTime = lastWheelEventTime;
+ }
+
+ @Override
+ public void onCrankMeasurementReceived(int crankRevolutions, int lastCrankEventTime) {
+ if (mLastCrankEventTime == lastCrankEventTime)
+ return;
+
+ if (mLastCrankRevolutions >= 0) {
+ float timeDifference = 0;
+ if (lastCrankEventTime < mLastCrankEventTime)
+ timeDifference = (65535 + lastCrankEventTime - mLastCrankEventTime) / 1024.0f; // [s]
+ else
+ timeDifference = (lastCrankEventTime - mLastCrankEventTime) / 1024.0f; // [s]
+
+ final float crankCadence = (crankRevolutions - mLastCrankRevolutions) * 60.0f / timeDifference;
+ if (crankCadence > 0) {
+ final float gearRatio = mWheelCadence / crankCadence;
+
+ final Intent broadcast = new Intent(BROADCAST_CRANK_DATA);
+ broadcast.putExtra(EXTRA_GEAR_RATIO, gearRatio);
+ broadcast.putExtra(EXTRA_CADENCE, (int) crankCadence);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+ }
+ mLastCrankRevolutions = crankRevolutions;
+ mLastCrankEventTime = lastCrankEventTime;
+ }
+
+ /**
+ * Creates the notification
+ *
+ * @param messageResId
+ * the message resource id. The message must have one String parameter,
+ * f.e. <string name="name">%s is connected</string>
+ * @param defaults
+ * signals that will be used to notify the user
+ */
+ private void createNotification(final int messageResId, final int defaults) {
+ final Intent parentIntent = new Intent(this, FeaturesActivity.class);
+ parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Intent targetIntent = new Intent(this, CSCActivity.class);
+
+ final Intent disconnect = new Intent(ACTION_DISCONNECT);
+ final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // both activities above have launchMode="singleTask" in the AndoridManifest.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);
+ 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);
+
+ final Notification notification = builder.build();
+ final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(NOTIFICATION_ID, notification);
+ }
+
+ /**
+ * Cancels the existing notification. If there is no active notification this method does nothing
+ */
+ private void cancelNotification() {
+ final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(NOTIFICATION_ID);
+ }
+
+ /**
+ * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
+ */
+ private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ Logger.i(getLogSession(), "[CSC] Disconnect action pressed");
+ if (isConnected())
+ getBinder().disconnect();
+ else
+ stopSelf();
+ }
+ };
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsActivity.java
new file mode 100644
index 000000000..a88e50a01
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsActivity.java
@@ -0,0 +1,50 @@
+/*
+ * 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.csc.settings;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.view.MenuItem;
+
+public class SettingsActivity extends ActionBarActivity {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ // Display the fragment as the main content.
+ getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ onBackPressed();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsFragment.java
new file mode 100644
index 000000000..e4e0f39a5
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsFragment.java
@@ -0,0 +1,75 @@
+/*
+ * 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.csc.settings;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+
+public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
+ public static final String SETTINGS_WHEEL_SIZE = "settings_wheel_size";
+ public static final int SETTINGS_WHEEL_SIZE_DEFAULT = 2340;
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.settings_csc);
+
+ // set initial values
+ updateWheelSizeSummary();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // attach the preference change listener. It will update the summary below interval preference
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // unregister listener
+ getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
+ if (SETTINGS_WHEEL_SIZE.equals(key)) {
+ updateWheelSizeSummary();
+ }
+ }
+
+ private void updateWheelSizeSummary() {
+ final PreferenceScreen screen = getPreferenceScreen();
+ final SharedPreferences preferences = getPreferenceManager().getSharedPreferences();
+
+ final String value = preferences.getString(SETTINGS_WHEEL_SIZE, String.valueOf(SETTINGS_WHEEL_SIZE_DEFAULT));
+ screen.findPreference(SETTINGS_WHEEL_SIZE).setSummary(getString(R.string.csc_settings_wheel_diameter_summary, value));
+ }
+}
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
new file mode 100644
index 000000000..1dfee4037
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuActivity.java
@@ -0,0 +1,923 @@
+/*
+ * 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.dfu;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.app.AlertDialog;
+import android.app.FragmentManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.app.NotificationManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.Loader;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+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.content.LocalBroadcastManager;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.webkit.MimeTypeMap;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import no.nordicsemi.android.error.GattError;
+import no.nordicsemi.android.nrftoolbox.AppHelpFragment;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.dfu.adapter.FileBrowserAppsAdapter;
+import no.nordicsemi.android.nrftoolbox.dfu.fragment.UploadCancelFragment;
+import no.nordicsemi.android.nrftoolbox.dfu.fragment.ZipInfoFragment;
+import no.nordicsemi.android.nrftoolbox.dfu.settings.SettingsActivity;
+import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+
+/**
+ * DfuActivity is the main DFU activity It implements DFUManagerCallbacks to receive callbacks from DFUManager class It implements
+ * DeviceScannerFragment.OnDeviceSelectedListener callback to receive callback when device is selected from scanning dialog The activity supports portrait and
+ * landscape orientations
+ */
+public class DfuActivity extends ActionBarActivity implements LoaderCallbacks, ScannerFragment.OnDeviceSelectedListener,
+ UploadCancelFragment.CancelFragmentListener {
+ 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 = 2;
+
+ 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";
+ private static final String PREFS_FILE_SIZE = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_FILE_SIZE";
+
+ private static final String DATA_DEVICE = "device";
+ private static final String DATA_FILE_TYPE = "file_type";
+ private static final String DATA_FILE_TYPE_TMP = "file_type_tmp";
+ private static final String DATA_FILE_PATH = "file_path";
+ private static final String DATA_FILE_STREAM = "file_stream";
+ private static final String DATA_INIT_FILE_PATH = "init_file_path";
+ private static final String DATA_INIT_FILE_STREAM = "init_file_stream";
+ private static final String DATA_STATUS = "status";
+
+ private static final String EXTRA_URI = "uri";
+
+ private static final int ENABLE_BT_REQ = 0;
+ private static final int SELECT_FILE_REQ = 1;
+ private static final int SELECT_INIT_FILE_REQ = 2;
+
+ private TextView mDeviceNameView;
+ private TextView mFileNameView;
+ private TextView mFileTypeView;
+ private TextView mFileSizeView;
+ private TextView mFileStatusView;
+ private TextView mTextPercentage;
+ private TextView mTextUploading;
+ private ProgressBar mProgressBar;
+
+ private Button mSelectFileButton, mUploadButton, mConnectButton;
+
+ private BluetoothDevice mSelectedDevice;
+ private String mFilePath;
+ private Uri mFileStreamUri;
+ private String mInitFilePath;
+ private Uri mInitFileStreamUri;
+ private int mFileType;
+ private int mFileTypeTmp; // This value is being used when user is selecting a file not to overwrite the old value (in case he/she will cancel selecting file)
+ private boolean mStatusOk;
+
+ private final BroadcastReceiver mDfuUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ // DFU is in progress or an error occurred
+ final String action = intent.getAction();
+
+ if (DfuService.BROADCAST_PROGRESS.equals(action)) {
+ final int progress = intent.getIntExtra(DfuService.EXTRA_DATA, 0);
+ final int currentPart = intent.getIntExtra(DfuService.EXTRA_PART_CURRENT, 1);
+ final int totalParts = intent.getIntExtra(DfuService.EXTRA_PARTS_TOTAL, 1);
+ updateProgressBar(progress, currentPart, totalParts, false);
+ } else if (DfuService.BROADCAST_ERROR.equals(action)) {
+ final int error = intent.getIntExtra(DfuService.EXTRA_DATA, 0);
+ updateProgressBar(error, 0, 0, true);
+
+ // We have to wait a bit before canceling notification. This is called before DfuService creates the last notification.
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ // if this activity is still open and upload process was completed, cancel the notification
+ final NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.cancel(DfuService.NOTIFICATION_ID);
+ }
+ }, 200);
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_feature_dfu);
+ isBLESupported();
+ if (!isBLEEnabled()) {
+ showBLEDialog();
+ }
+ setGUI();
+
+ ensureSamplesExist();
+
+ // restore saved state
+ mFileType = DfuService.TYPE_APPLICATION; // Default
+ if (savedInstanceState != null) {
+ mFileType = savedInstanceState.getInt(DATA_FILE_TYPE);
+ mFileTypeTmp = savedInstanceState.getInt(DATA_FILE_TYPE_TMP);
+ mFilePath = savedInstanceState.getString(DATA_FILE_PATH);
+ mFileStreamUri = savedInstanceState.getParcelable(DATA_FILE_STREAM);
+ mInitFilePath = savedInstanceState.getString(DATA_INIT_FILE_PATH);
+ mInitFileStreamUri = savedInstanceState.getParcelable(DATA_INIT_FILE_STREAM);
+ mSelectedDevice = savedInstanceState.getParcelable(DATA_DEVICE);
+ mStatusOk = mStatusOk || savedInstanceState.getBoolean(DATA_STATUS);
+ mUploadButton.setEnabled(mSelectedDevice != null && mStatusOk);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(DATA_FILE_TYPE, mFileType);
+ outState.putInt(DATA_FILE_TYPE_TMP, mFileTypeTmp);
+ outState.putString(DATA_FILE_PATH, mFilePath);
+ outState.putParcelable(DATA_FILE_STREAM, mFileStreamUri);
+ outState.putString(DATA_INIT_FILE_PATH, mInitFilePath);
+ outState.putParcelable(DATA_INIT_FILE_STREAM, mInitFileStreamUri);
+ outState.putParcelable(DATA_DEVICE, mSelectedDevice);
+ outState.putBoolean(DATA_STATUS, mStatusOk);
+ }
+
+ private void setGUI() {
+ final ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ mDeviceNameView = (TextView) findViewById(R.id.device_name);
+ mFileNameView = (TextView) findViewById(R.id.file_name);
+ mFileTypeView = (TextView) findViewById(R.id.file_type);
+ mFileSizeView = (TextView) findViewById(R.id.file_size);
+ mFileStatusView = (TextView) findViewById(R.id.file_status);
+ mSelectFileButton = (Button) findViewById(R.id.action_select_file);
+
+ mUploadButton = (Button) findViewById(R.id.action_upload);
+ mConnectButton = (Button) findViewById(R.id.action_connect);
+ mTextPercentage = (TextView) findViewById(R.id.textviewProgress);
+ mTextUploading = (TextView) findViewById(R.id.textviewUploading);
+ mProgressBar = (ProgressBar) findViewById(R.id.progressbar_file);
+
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ if (isDfuServiceRunning()) {
+ // Restore image file information
+ mDeviceNameView.setText(preferences.getString(PREFS_DEVICE_NAME, ""));
+ mFileNameView.setText(preferences.getString(PREFS_FILE_NAME, ""));
+ mFileTypeView.setText(preferences.getString(PREFS_FILE_TYPE, ""));
+ mFileSizeView.setText(preferences.getString(PREFS_FILE_SIZE, ""));
+ mFileStatusView.setText(R.string.dfu_file_status_ok);
+ mStatusOk = true;
+ showProgressBar();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // We are using LocalBroadcastReceiver instead of normal BroadcastReceiver for optimization purposes
+ final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
+ broadcastManager.registerReceiver(mDfuUpdateReceiver, makeDfuUpdateIntentFilter());
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
+ broadcastManager.unregisterReceiver(mDfuUpdateReceiver);
+ }
+
+ private static IntentFilter makeDfuUpdateIntentFilter() {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(DfuService.BROADCAST_PROGRESS);
+ intentFilter.addAction(DfuService.BROADCAST_ERROR);
+ intentFilter.addAction(DfuService.BROADCAST_LOG);
+ return intentFilter;
+ }
+
+ private void isBLESupported() {
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ showToast(R.string.no_ble);
+ finish();
+ }
+ }
+
+ private boolean isBLEEnabled() {
+ final BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ final BluetoothAdapter adapter = manager.getAdapter();
+ return adapter != null && adapter.isEnabled();
+ }
+
+ private void showBLEDialog() {
+ final Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(enableIntent, ENABLE_BT_REQ);
+ }
+
+ private void showDeviceScanningDialog() {
+ final FragmentManager fm = getFragmentManager();
+ 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.
+ dialog.show(fm, "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 modified - bug fixed
+ new File(root, "dfu_2_0.sh").delete(); // This file has been migrated to 3.0
+ 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;
+ }
+
+ 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_0.bat");
+ if (!f.exists()) {
+ copyRawResource(R.raw.dfu_win_3_0, f);
+ newCopied = true;
+ }
+ f = new File(root, "dfu_3_0.sh");
+ if (!f.exists()) {
+ copyRawResource(R.raw.dfu_mac_3_0, 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.dfu_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ onBackPressed();
+ break;
+ case R.id.action_about:
+ final AppHelpFragment fragment = AppHelpFragment.getInstance(R.string.dfu_about_text);
+ fragment.show(getFragmentManager(), "help_fragment");
+ break;
+ case R.id.action_settings:
+ final Intent intent = new Intent(this, SettingsActivity.class);
+ startActivity(intent);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
+ if (resultCode != RESULT_OK)
+ return;
+
+ switch (requestCode) {
+ case SELECT_FILE_REQ: {
+ // clear previous data
+ mFileType = mFileTypeTmp;
+ mFilePath = null;
+ mFileStreamUri = null;
+
+ // and read new one
+ 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 by 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();
+ final File file = new File(path);
+ mFilePath = path;
+
+ updateFileInfo(file.getName(), file.length(), mFileType);
+ } else if (uri.getScheme().equals("content")) {
+ // an Uri has been returned
+ mFileStreamUri = uri;
+ // if application returned Uri for streaming, let's us it. Does it works?
+ // FIXME both Uris works with Google Drive app. Why both? What's the difference? How about other apps like DropBox?
+ final Bundle extras = data.getExtras();
+ if (extras != null && extras.containsKey(Intent.EXTRA_STREAM))
+ mFileStreamUri = extras.getParcelable(Intent.EXTRA_STREAM);
+
+ // file name and size must be obtained from Content Provider
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(EXTRA_URI, uri);
+ getLoaderManager().restartLoader(SELECT_FILE_REQ, bundle, this);
+ }
+ break;
+ }
+ case SELECT_INIT_FILE_REQ: {
+ mInitFilePath = null;
+ mInitFileStreamUri = null;
+
+ // and read new one
+ 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 by Content Provider. To do that we are using a Loader.
+ */
+ if (uri.getScheme().equals("file")) {
+ // the direct path to the file has been returned
+ mInitFilePath = uri.getPath();
+ mFileStatusView.setText(R.string.dfu_file_status_ok_with_init);
+ } else if (uri.getScheme().equals("content")) {
+ // an Uri has been returned
+ mInitFileStreamUri = uri;
+ // if application returned Uri for streaming, let's us it. Does it works?
+ // FIXME both Uris works with Google Drive app. Why both? What's the difference? How about other apps like DropBox?
+ final Bundle extras = data.getExtras();
+ if (extras != null && extras.containsKey(Intent.EXTRA_STREAM))
+ mInitFileStreamUri = extras.getParcelable(Intent.EXTRA_STREAM);
+ mFileStatusView.setText(R.string.dfu_file_status_ok_with_init);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public Loader onCreateLoader(final int id, final Bundle args) {
+ final Uri uri = args.getParcelable(EXTRA_URI);
+ /*
+ * Some apps, f.e. Google Drive allow to select file that is not on the device. There is no "_data" column handled by that provider. Let's try to obtain
+ * all columns and than check which columns are present.
+ */
+ // final String[] projection = new String[] { MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DATA };
+ return new CursorLoader(this, uri, null /* all columns, instead of projection */, null, null, null);
+ }
+
+ @Override
+ public void onLoaderReset(final Loader loader) {
+ mFileNameView.setText(null);
+ mFileTypeView.setText(null);
+ mFileSizeView.setText(null);
+ mFilePath = null;
+ mFileStreamUri = null;
+ mStatusOk = false;
+ }
+
+ @Override
+ public void onLoadFinished(final Loader loader, final Cursor data) {
+ if (data != null && data.moveToNext()) {
+ /*
+ * Here we have to check the column indexes by name as we have requested for all. The order may be different.
+ */
+ final String fileName = data.getString(data.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)/* 0 DISPLAY_NAME */);
+ final int fileSize = data.getInt(data.getColumnIndex(MediaStore.MediaColumns.SIZE) /* 1 SIZE */);
+ String filePath = null;
+ final int dataIndex = data.getColumnIndex(MediaStore.MediaColumns.DATA);
+ if (dataIndex != -1)
+ filePath = data.getString(dataIndex /* 2 DATA */);
+ if (!TextUtils.isEmpty(filePath))
+ mFilePath = filePath;
+
+ updateFileInfo(fileName, fileSize, mFileType);
+ } else {
+ mFileNameView.setText(null);
+ mFileTypeView.setText(null);
+ mFileSizeView.setText(null);
+ mFilePath = null;
+ mFileStreamUri = null;
+ mFileStatusView.setText(R.string.dfu_file_status_error);
+ mStatusOk = false;
+ }
+ }
+
+ /**
+ * Updates the file information on UI
+ *
+ * @param fileName file name
+ * @param fileSize file length
+ */
+ private void updateFileInfo(final String fileName, final long fileSize, final int fileType) {
+ mFileNameView.setText(fileName);
+ switch (fileType) {
+ case DfuService.TYPE_SOFT_DEVICE:
+ mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[0]);
+ break;
+ case DfuService.TYPE_BOOTLOADER:
+ mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[1]);
+ break;
+ case DfuService.TYPE_APPLICATION:
+ mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[2]);
+ break;
+ case DfuService.TYPE_AUTO:
+ mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[3]);
+ break;
+ }
+ mFileSizeView.setText(getString(R.string.dfu_file_size_text, fileSize));
+ final String extension = mFileType == DfuService.TYPE_AUTO ? "(?i)ZIP" : "(?i)HEX|BIN"; // (?i) = case insensitive
+ final boolean statusOk = mStatusOk = MimeTypeMap.getFileExtensionFromUrl(fileName).matches(extension);
+ mFileStatusView.setText(statusOk ? R.string.dfu_file_status_ok : R.string.dfu_file_status_invalid);
+ mUploadButton.setEnabled(mSelectedDevice != null && statusOk);
+
+ // Ask the user for the Init packet file if HEX or BIN files are selected. In case of a ZIP file the Init packets should be included in the ZIP.
+ if (statusOk && fileType != DfuService.TYPE_AUTO) {
+ new AlertDialog.Builder(this).setTitle(R.string.dfu_file_init_title).setMessage(R.string.dfu_file_init_message)
+ .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ mInitFilePath = null;
+ mInitFileStreamUri = null;
+ }
+ }).setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType(DfuService.MIME_TYPE_OCTET_STREAM);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ startActivityForResult(intent, SELECT_INIT_FILE_REQ);
+ }
+ }).show();
+ }
+ }
+
+ /**
+ * Called when the question mark was pressed
+ *
+ * @param view a button that was pressed
+ */
+ public void onSelectFileHelpClicked(final View view) {
+ new AlertDialog.Builder(this).setTitle(R.string.dfu_help_title).setMessage(R.string.dfu_help_message).setPositiveButton(R.string.ok, null)
+ .show();
+ }
+
+ /**
+ * Called when Select File was pressed
+ *
+ * @param view a button that was pressed
+ */
+ public void onSelectFileClicked(final View view) {
+ mFileTypeTmp = mFileType;
+ int index = 2;
+ switch (mFileType) {
+ case DfuService.TYPE_SOFT_DEVICE:
+ index = 0;
+ break;
+ case DfuService.TYPE_BOOTLOADER:
+ index = 1;
+ break;
+ case DfuService.TYPE_APPLICATION:
+ index = 2;
+ break;
+ case DfuService.TYPE_AUTO:
+ index = 3;
+ break;
+ }
+ // Show a dialog with file types
+ new AlertDialog.Builder(this).setTitle(R.string.dfu_file_type_title)
+ .setSingleChoiceItems(R.array.dfu_file_type, index, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ switch (which) {
+ case 0:
+ mFileTypeTmp = DfuService.TYPE_SOFT_DEVICE;
+ break;
+ case 1:
+ mFileTypeTmp = DfuService.TYPE_BOOTLOADER;
+ break;
+ case 2:
+ mFileTypeTmp = DfuService.TYPE_APPLICATION;
+ break;
+ case 3:
+ mFileTypeTmp = DfuService.TYPE_AUTO;
+ break;
+ }
+ }
+ }).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ openFileChooser();
+ }
+ }).setNeutralButton(R.string.dfu_file_info, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ final ZipInfoFragment fragment = new ZipInfoFragment();
+ fragment.show(getFragmentManager(), "help_fragment");
+ }
+ }).setNegativeButton(R.string.cancel, null).show();
+ }
+
+ private void openFileChooser() {
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType(mFileTypeTmp == DfuService.TYPE_AUTO ? DfuService.MIME_TYPE_ZIP : DfuService.MIME_TYPE_OCTET_STREAM);
+ 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.ok, 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();
+ }
+ }
+
+ /**
+ * Callback of UPDATE/CANCEL button on DfuActivity
+ */
+ public void onUploadClicked(final View view) {
+ if (isDfuServiceRunning()) {
+ showUploadCancelDialog();
+ return;
+ }
+
+ // Check whether the selected file is a HEX file (we are just checking the extension)
+ if (!mStatusOk) {
+ Toast.makeText(this, R.string.dfu_file_status_invalid_message, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ // Save current state in order to restore it if user quit the Activity
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ final SharedPreferences.Editor editor = preferences.edit();
+ editor.putString(PREFS_DEVICE_NAME, mSelectedDevice.getName());
+ editor.putString(PREFS_FILE_NAME, mFileNameView.getText().toString());
+ editor.putString(PREFS_FILE_TYPE, mFileTypeView.getText().toString());
+ editor.putString(PREFS_FILE_SIZE, mFileSizeView.getText().toString());
+ editor.apply();
+
+ showProgressBar();
+
+ final Intent service = new Intent(this, DfuService.class);
+ service.putExtra(DfuService.EXTRA_DEVICE_ADDRESS, mSelectedDevice.getAddress());
+ service.putExtra(DfuService.EXTRA_DEVICE_NAME, mSelectedDevice.getName());
+ service.putExtra(DfuService.EXTRA_FILE_MIME_TYPE, mFileType == DfuService.TYPE_AUTO ? DfuService.MIME_TYPE_ZIP : DfuService.MIME_TYPE_OCTET_STREAM);
+ service.putExtra(DfuService.EXTRA_FILE_TYPE, mFileType);
+ service.putExtra(DfuService.EXTRA_FILE_PATH, mFilePath);
+ service.putExtra(DfuService.EXTRA_FILE_URI, mFileStreamUri);
+ service.putExtra(DfuService.EXTRA_INIT_FILE_PATH, mInitFilePath);
+ service.putExtra(DfuService.EXTRA_INIT_FILE_URI, mInitFileStreamUri);
+ startService(service);
+ }
+
+ private void showUploadCancelDialog() {
+ final LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
+ final Intent pauseAction = new Intent(DfuService.BROADCAST_ACTION);
+ pauseAction.putExtra(DfuService.EXTRA_ACTION, DfuService.ACTION_PAUSE);
+ manager.sendBroadcast(pauseAction);
+
+ final UploadCancelFragment fragment = UploadCancelFragment.getInstance();
+ fragment.show(getFragmentManager(), TAG);
+ }
+
+ /**
+ * Callback of CONNECT/DISCONNECT button on DfuActivity
+ */
+ public void onConnectClicked(final View view) {
+ if (isBLEEnabled()) {
+ showDeviceScanningDialog();
+ } else {
+ showBLEDialog();
+ }
+ }
+
+ @Override
+ public void onDeviceSelected(final BluetoothDevice device, final String name) {
+ mSelectedDevice = device;
+ mUploadButton.setEnabled(mStatusOk);
+ mDeviceNameView.setText(name);
+ }
+
+ @Override
+ public void onDialogCanceled() {
+ // do nothing
+ }
+
+ private void updateProgressBar(final int progress, final int part, final int total, final boolean error) {
+ switch (progress) {
+ case DfuService.PROGRESS_CONNECTING:
+ mProgressBar.setIndeterminate(true);
+ mTextPercentage.setText(R.string.dfu_status_connecting);
+ break;
+ case DfuService.PROGRESS_STARTING:
+ mProgressBar.setIndeterminate(true);
+ mTextPercentage.setText(R.string.dfu_status_starting);
+ break;
+ case DfuService.PROGRESS_ENABLING_DFU_MODE:
+ mProgressBar.setIndeterminate(true);
+ mTextPercentage.setText(R.string.dfu_status_switching_to_dfu);
+ break;
+ case DfuService.PROGRESS_VALIDATING:
+ mProgressBar.setIndeterminate(true);
+ mTextPercentage.setText(R.string.dfu_status_validating);
+ break;
+ case DfuService.PROGRESS_DISCONNECTING:
+ mProgressBar.setIndeterminate(true);
+ mTextPercentage.setText(R.string.dfu_status_disconnecting);
+ break;
+ case DfuService.PROGRESS_COMPLETED:
+ mTextPercentage.setText(R.string.dfu_status_completed);
+ // let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again.
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ onTransferCompleted();
+
+ // if this activity is still open and upload process was completed, cancel the notification
+ final NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.cancel(DfuService.NOTIFICATION_ID);
+ }
+ }, 200);
+ break;
+ case DfuService.PROGRESS_ABORTED:
+ mTextPercentage.setText(R.string.dfu_status_aborted);
+ // let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again.
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ onUploadCanceled();
+
+ // if this activity is still open and upload process was completed, cancel the notification
+ final NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.cancel(DfuService.NOTIFICATION_ID);
+ }
+ }, 200);
+ break;
+ default:
+ mProgressBar.setIndeterminate(false);
+ if (error) {
+ showErrorMessage(progress);
+ } else {
+ mProgressBar.setProgress(progress);
+ mTextPercentage.setText(getString(R.string.progress, progress));
+ if (total > 1)
+ mTextUploading.setText(getString(R.string.dfu_status_uploading_part, part, total));
+ else
+ mTextUploading.setText(R.string.dfu_status_uploading);
+ }
+ break;
+ }
+ }
+
+ private void showProgressBar() {
+ mProgressBar.setVisibility(View.VISIBLE);
+ mTextPercentage.setVisibility(View.VISIBLE);
+ mTextPercentage.setText(null);
+ mTextUploading.setText(R.string.dfu_status_uploading);
+ mTextUploading.setVisibility(View.VISIBLE);
+ mConnectButton.setEnabled(false);
+ mSelectFileButton.setEnabled(false);
+ mUploadButton.setEnabled(true);
+ mUploadButton.setText(R.string.dfu_action_upload_cancel);
+ }
+
+ private void onTransferCompleted() {
+ clearUI(true);
+ showToast(R.string.dfu_success);
+ }
+
+ public void onUploadCanceled() {
+ clearUI(false);
+ showToast(R.string.dfu_aborted);
+ }
+
+ @Override
+ public void onCancelUpload() {
+ mProgressBar.setIndeterminate(true);
+ mTextUploading.setText(R.string.dfu_status_aborting);
+ mTextPercentage.setText(null);
+ }
+
+ private void showErrorMessage(final int code) {
+ clearUI(false);
+ showToast("Upload failed: " + GattError.parse(code) + " (" + (code & ~(DfuService.ERROR_MASK | DfuService.ERROR_REMOTE_MASK)) + ")");
+ }
+
+ private void clearUI(final boolean clearDevice) {
+ mProgressBar.setVisibility(View.INVISIBLE);
+ mTextPercentage.setVisibility(View.INVISIBLE);
+ mTextUploading.setVisibility(View.INVISIBLE);
+ mConnectButton.setEnabled(true);
+ mSelectFileButton.setEnabled(true);
+ mUploadButton.setEnabled(false);
+ mUploadButton.setText(R.string.dfu_action_upload);
+ if (clearDevice) {
+ mSelectedDevice = null;
+ mDeviceNameView.setText(R.string.dfu_default_name);
+ }
+ // Application may have lost the right to these files if Activity was closed during upload (grant uri permission). Clear file related values.
+ mFileNameView.setText(null);
+ mFileTypeView.setText(null);
+ mFileSizeView.setText(null);
+ mFileStatusView.setText(R.string.dfu_file_status_no_file);
+ mFilePath = null;
+ mFileStreamUri = null;
+ mInitFilePath = null;
+ mInitFileStreamUri = null;
+ mStatusOk = false;
+ }
+
+ private void showToast(final int messageResId) {
+ Toast.makeText(this, messageResId, Toast.LENGTH_SHORT).show();
+ }
+
+ private void showToast(final String message) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ }
+
+ private boolean isDfuServiceRunning() {
+ ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+ for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+ if (DfuService.class.getName().equals(service.service.getClassName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
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
new file mode 100644
index 000000000..7f222b77d
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuInitiatorActivity.java
@@ -0,0 +1,78 @@
+/*
+ * 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.dfu;
+
+import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment;
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * The activity is started only by a remote connected computer using ADB. It shows a list of DFU-supported devices in range and allows user to select target device. The HEX file will be uploaded to
+ * selected device using {@link DfuService}.
+ */
+public class DfuInitiatorActivity extends Activity implements ScannerFragment.OnDeviceSelectedListener {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // The activity must be started with a path to the HEX file
+ final Intent intent = getIntent();
+ if (!intent.hasExtra(DfuService.EXTRA_FILE_PATH))
+ 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.
+ fragment.show(getFragmentManager(), null);
+ }
+ }
+
+ @Override
+ public void onDeviceSelected(final BluetoothDevice device, final String name) {
+ final Intent intent = getIntent();
+ final String overwrittenName = intent.getStringExtra(DfuService.EXTRA_DEVICE_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 int type = intent.getIntExtra(DfuService.EXTRA_FILE_TYPE, DfuService.TYPE_AUTO);
+
+ // Start DFU service with data provided in the intent
+ final Intent service = new Intent(this, DfuService.class);
+ service.putExtra(DfuService.EXTRA_DEVICE_ADDRESS, address);
+ service.putExtra(DfuService.EXTRA_DEVICE_NAME, finalName);
+ service.putExtra(DfuService.EXTRA_FILE_TYPE, type);
+ service.putExtra(DfuService.EXTRA_FILE_PATH, path);
+ if (intent.hasExtra(DfuService.EXTRA_INIT_FILE_PATH))
+ service.putExtra(DfuService.EXTRA_INIT_FILE_PATH, initPath);
+ startService(service);
+ finish();
+ }
+
+ @Override
+ public void onDialogCanceled() {
+ finish();
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuService.java
new file mode 100644
index 000000000..b569a967c
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuService.java
@@ -0,0 +1,48 @@
+/*
+ * 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.dfu;
+
+import no.nordicsemi.android.dfu.DfuBaseService;
+import android.app.Activity;
+
+public class DfuService extends DfuBaseService {
+
+ @Override
+ protected Class extends Activity> getNotificationTarget() {
+ /*
+ * As a target activity the NotificationActivity is returned, not the MainActivity. This is because the notification must create a new task:
+ *
+ * intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ *
+ * when user press it. Using NotificationActivity we can check whether the new activity is a root activity (that means no other activity was open before)
+ * or that there is other activity already open. In the later case the notificationActivity will just be closed. System will restore the previous activity.
+ * However if the application has been closed during upload and user click the notification a NotificationActivity will be launched as a root activity.
+ * It will create and start the main activity and terminate itself.
+ *
+ * This method may be used to restore the target activity in case the application was closed or is open. It may also be used to recreate an activity
+ * history (see NotificationActivity).
+ */
+ return NotificationActivity.class;
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/NotificationActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/NotificationActivity.java
new file mode 100644
index 000000000..c60347102
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/NotificationActivity.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dfu;
+
+import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class NotificationActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // If this activity is the root activity of the task, the app is not running
+ if (isTaskRoot()) {
+ // Start the app before finishing
+ final Intent parentIntent = new Intent(this, FeaturesActivity.class);
+ parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Intent startAppIntent = new Intent(this, DfuActivity.class);
+ startAppIntent.putExtras(getIntent().getExtras());
+ startActivities(new Intent[] { parentIntent, startAppIntent });
+ }
+
+ // Now finish, which will drop the user in to the activity that was at the top
+ // of the task stack
+ finish();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/adapter/FileBrowserAppsAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/adapter/FileBrowserAppsAdapter.java
new file mode 100644
index 000000000..cfe2fba32
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/adapter/FileBrowserAppsAdapter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.dfu.adapter;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+/**
+ * This adapter displays some file browser applications that can be used to select HEX file. It is used when there is no such app already installed on the device. The hardcoded apps and Google Play
+ * URLs are specified in res/values/strings_dfu.xml.
+ */
+public class FileBrowserAppsAdapter extends BaseAdapter {
+ private final LayoutInflater mInflater;
+ private final Resources mResources;
+
+ public FileBrowserAppsAdapter(final Context context) {
+ mInflater = LayoutInflater.from(context);
+ mResources = context.getResources();
+ }
+
+ @Override
+ public int getCount() {
+ return mResources.getStringArray(R.array.dfu_app_file_browser).length;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mResources.getStringArray(R.array.dfu_app_file_browser_action)[position];
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = mInflater.inflate(R.layout.app_file_browser_item, parent, false);
+ }
+
+ final TextView item = (TextView) view;
+ item.setText(mResources.getStringArray(R.array.dfu_app_file_browser)[position]);
+ item.getCompoundDrawablesRelative()[0].setLevel(position);
+ return view;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/UploadCancelFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/UploadCancelFragment.java
new file mode 100644
index 000000000..3f6531895
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/UploadCancelFragment.java
@@ -0,0 +1,91 @@
+/*
+ * 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.dfu.fragment;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.dfu.DfuService;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+/**
+ * When cancel button is pressed during uploading this fragment shows uploading cancel dialog
+ */
+public class UploadCancelFragment extends DialogFragment {
+ private static final String TAG = "UploadCancelFragment";
+
+ private CancelFragmentListener mListener;
+
+ public interface CancelFragmentListener {
+ public void onCancelUpload();
+ }
+
+ public static UploadCancelFragment getInstance() {
+ return new UploadCancelFragment();
+ }
+
+ @Override
+ public void onAttach(final Activity activity) {
+ super.onAttach(activity);
+
+ try {
+ mListener = (CancelFragmentListener) activity;
+ } catch (final ClassCastException e) {
+ Log.d(TAG, "The parent Activity must implement CancelFragmentListener interface");
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity()).setTitle(R.string.dfu_confirmation_dialog_title).setMessage(R.string.dfu_upload_dialog_cancel_message).setCancelable(false)
+ .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int whichButton) {
+ final LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getActivity());
+ final Intent pauseAction = new Intent(DfuService.BROADCAST_ACTION);
+ pauseAction.putExtra(DfuService.EXTRA_ACTION, DfuService.ACTION_ABORT);
+ manager.sendBroadcast(pauseAction);
+
+ mListener.onCancelUpload();
+ }
+ }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ dialog.cancel();
+ }
+ }).create();
+ }
+
+ @Override
+ public void onCancel(final DialogInterface dialog) {
+ final LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getActivity());
+ final Intent pauseAction = new Intent(DfuService.BROADCAST_ACTION);
+ pauseAction.putExtra(DfuService.EXTRA_ACTION, DfuService.ACTION_RESUME);
+ manager.sendBroadcast(pauseAction);
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/ZipInfoFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/ZipInfoFragment.java
new file mode 100644
index 000000000..e3b6a3eca
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/ZipInfoFragment.java
@@ -0,0 +1,39 @@
+/*
+ * 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.dfu.fragment;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+
+public class ZipInfoFragment extends DialogFragment {
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_zip_info, null);
+ return new AlertDialog.Builder(getActivity()).setView(view).setTitle(R.string.dfu_file_info).setPositiveButton(R.string.ok, null).create();
+ }
+}
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
new file mode 100644
index 000000000..6c8189650
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/AboutDfuPreference.java
@@ -0,0 +1,57 @@
+/*
+ * 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.dfu.settings;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.widget.Toast;
+
+public class AboutDfuPreference extends Preference {
+
+ public AboutDfuPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AboutDfuPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, 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/7.1.0/s110/html/a00062.html"));
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // is browser installed?
+ if (intent.resolveActivity(context.getPackageManager()) != null)
+ context.startActivity(intent);
+ else {
+ Toast.makeText(getContext(), R.string.no_application, Toast.LENGTH_LONG).show();
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsActivity.java
new file mode 100644
index 000000000..e9614fc74
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsActivity.java
@@ -0,0 +1,50 @@
+/*
+ * 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.dfu.settings;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.view.MenuItem;
+
+public class SettingsActivity extends ActionBarActivity {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ // Display the fragment as the main content.
+ getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ onBackPressed();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsFragment.java
new file mode 100644
index 000000000..c98d191c3
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsFragment.java
@@ -0,0 +1,100 @@
+/*
+ * 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.dfu.settings;
+
+import no.nordicsemi.android.dfu.DfuSettingsConstants;
+import no.nordicsemi.android.nrftoolbox.R;
+import android.app.AlertDialog;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+
+public class SettingsFragment extends PreferenceFragment implements DfuSettingsConstants, SharedPreferences.OnSharedPreferenceChangeListener {
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.settings_dfu);
+
+ // set initial values
+ updateNumberOfPacketsSummary();
+ updateMBRSize();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // attach the preference change listener. It will update the summary below interval preference
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // unregister listener
+ getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
+ final SharedPreferences preferences = getPreferenceManager().getSharedPreferences();
+
+ if (SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED.equals(key)) {
+ final boolean disabled = !preferences.getBoolean(SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED, true);
+ if (disabled) {
+ new AlertDialog.Builder(getActivity()).setMessage(R.string.dfu_settings_dfu_number_of_packets_info).setTitle(R.string.dfu_settings_dfu_information)
+ .setNeutralButton(R.string.ok, null).show();
+ }
+ } else if (SETTINGS_NUMBER_OF_PACKETS.equals(key)) {
+ updateNumberOfPacketsSummary();
+ } else if (SETTINGS_MBR_SIZE.equals(key)) {
+ updateMBRSize();
+ }
+ }
+
+ private void updateNumberOfPacketsSummary() {
+ final PreferenceScreen screen = getPreferenceScreen();
+ final SharedPreferences preferences = getPreferenceManager().getSharedPreferences();
+
+ final String value = preferences.getString(SETTINGS_NUMBER_OF_PACKETS, String.valueOf(SETTINGS_NUMBER_OF_PACKETS_DEFAULT));
+ screen.findPreference(SETTINGS_NUMBER_OF_PACKETS).setSummary(value);
+
+ final int valueInt = Integer.parseInt(value);
+ if (valueInt > 200) {
+ new AlertDialog.Builder(getActivity()).setMessage(R.string.dfu_settings_dfu_number_of_packets_info).setTitle(R.string.dfu_settings_dfu_information)
+ .setNeutralButton(R.string.ok, null)
+ .show();
+ }
+ }
+
+ private void updateMBRSize() {
+ final PreferenceScreen screen = getPreferenceScreen();
+ final SharedPreferences preferences = getPreferenceManager().getSharedPreferences();
+
+ final String value = preferences.getString(SETTINGS_MBR_SIZE, String.valueOf(SETTINGS_DEFAULT_MBR_SIZE));
+ screen.findPreference(SETTINGS_MBR_SIZE).setSummary(value);
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/ExpandableRecordAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/ExpandableRecordAdapter.java
new file mode 100644
index 000000000..a4bc807d7
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/ExpandableRecordAdapter.java
@@ -0,0 +1,206 @@
+/*
+ * 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.gls;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.TextView;
+
+public class ExpandableRecordAdapter extends BaseExpandableListAdapter {
+ private final GlucoseManager mGlucoseManager;
+ private final LayoutInflater mInflater;
+ private final Context mContext;
+
+ public ExpandableRecordAdapter(final Context context, final GlucoseManager manager) {
+ mGlucoseManager = manager;
+ mContext = context;
+ mInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public int getGroupCount() {
+ return mGlucoseManager.getRecords().size();
+ }
+
+ @Override
+ public Object getGroup(int groupPosition) {
+ return mGlucoseManager.getRecords().valueAt(groupPosition);
+ }
+
+ @Override
+ public long getGroupId(final int groupPosition) {
+ return mGlucoseManager.getRecords().keyAt(groupPosition);
+ }
+
+ @Override
+ public View getGroupView(final int position, boolean isExpanded, final View convertView, final ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = mInflater.inflate(R.layout.activity_feature_gls_item, parent, false);
+
+ final GroupViewHolder holder = new GroupViewHolder();
+ holder.time = (TextView) view.findViewById(R.id.time);
+ holder.details = (TextView) view.findViewById(R.id.details);
+ holder.concentration = (TextView) view.findViewById(R.id.gls_concentration);
+ view.setTag(holder);
+ }
+ final GlucoseRecord record = (GlucoseRecord) getGroup(position);
+ if (record == null)
+ return view; // this may happen during closing the activity
+ final GroupViewHolder holder = (GroupViewHolder) view.getTag();
+ holder.time.setText(mContext.getString(R.string.gls_timestamp, record.time));
+ try {
+ holder.details.setText(mContext.getResources().getStringArray(R.array.gls_type)[record.type]);
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ holder.details.setText(mContext.getResources().getStringArray(R.array.gls_type)[0]);
+ }
+ if (record.unit == GlucoseRecord.UNIT_kgpl) {
+ holder.concentration.setText(mContext.getString(R.string.gls_value, record.glucoseConcentration * 100000.0f));
+ } else {
+ holder.concentration.setText(mContext.getString(R.string.gls_value, record.glucoseConcentration * 1000.0f));
+ }
+ return view;
+ }
+
+ @Override
+ public int getChildrenCount(final int groupPosition) {
+ final GlucoseRecord record = (GlucoseRecord) getGroup(groupPosition);
+ int count = 1 + (record.status != 0 ? 1 : 0); // Sample Location and Sensor Status Annunciation
+ if (record.context != null) {
+ final GlucoseRecord.MeasurementContext context = record.context;
+ if (context.carbohydrateId != 0)
+ count += 1; // Carbohydrate ID and units
+ if (context.meal != 0)
+ count += 1; // Meal
+ if (context.tester != 0)
+ count += 1; // Tester
+ if (context.health != 0)
+ count += 1; // Health
+ if (context.exerciseDuration != 0)
+ count += 1; // Duration and intensity
+ if (context.medicationId != 0)
+ count += 1; // Medication ID and quantity (with unit)
+ if (context.HbA1c != 0)
+ count += 1; // HbA1c
+ }
+ return count;
+ }
+
+ @Override
+ public Object getChild(final int groupPosition, final int childPosition) {
+ final Resources resources = mContext.getResources();
+ final GlucoseRecord record = (GlucoseRecord) getGroup(groupPosition);
+ switch (childPosition) {
+ case 0:
+ String location = null;
+ try {
+ location = resources.getStringArray(R.array.gls_location)[record.sampleLocation];
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ location = resources.getStringArray(R.array.gls_location)[0];
+ }
+ return new Pair<>(resources.getString(R.string.gls_location_title), location);
+ case 1: { // sensor status annunciation
+ final StringBuilder builder = new StringBuilder();
+ final int status = record.status;
+ for (int i = 0; i < 12; ++i)
+ if ((status & (1 << i)) > 0)
+ builder.append(resources.getStringArray(R.array.gls_status_annunciatioin)[i]).append("\n");
+ builder.setLength(builder.length() - 1);
+ return new Pair<>(resources.getString(R.string.gls_status_annunciatioin_title), builder.toString());
+ }
+ case 2: { // carbohydrate id and unit
+ final StringBuilder builder = new StringBuilder();
+ builder.append(resources.getStringArray(R.array.gls_context_carbohydrare)[record.context.carbohydrateId]).append(" (" + record.context.carbohydrateUnits + " kg)");
+ return new Pair<>(resources.getString(R.string.gls_context_carbohydrare_title), builder.toString());
+ }
+ case 3: { // meal
+ final StringBuilder builder = new StringBuilder();
+ builder.append(resources.getStringArray(R.array.gls_context_meal)[record.context.meal]);
+ return new Pair<>(resources.getString(R.string.gls_context_meal_title), builder.toString());
+ }
+ case 4: { // tester
+ final StringBuilder builder = new StringBuilder();
+ builder.append(resources.getStringArray(R.array.gls_context_tester)[record.context.tester]);
+ return new Pair<>(resources.getString(R.string.gls_context_tester_title), builder.toString());
+ }
+ case 5: { // health
+ final StringBuilder builder = new StringBuilder();
+ builder.append(resources.getStringArray(R.array.gls_context_health)[record.context.health]);
+ return new Pair<>(resources.getString(R.string.gls_context_health_title), builder.toString());
+ }
+ default:
+ // TODO write parsers for other properties
+ return new Pair<>("Not implemented", "The value exists but is not shown");
+ }
+ }
+
+ @Override
+ public long getChildId(final int groupPosition, final int childPosition) {
+ return groupPosition + childPosition;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public View getChildView(final int groupPosition, final int childPosition, final boolean isLastChild, final View convertView, final ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = mInflater.inflate(R.layout.activity_feature_gls_subitem, parent, false);
+ final ChildViewHolder holder = new ChildViewHolder();
+ holder.title = (TextView) view.findViewById(android.R.id.text1);
+ holder.details = (TextView) view.findViewById(android.R.id.text2);
+ view.setTag(holder);
+ }
+ final Pair value = (Pair) getChild(groupPosition, childPosition);
+ final ChildViewHolder holder = (ChildViewHolder) view.getTag();
+ holder.title.setText(value.first);
+ holder.details.setText(value.second);
+ return view;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public boolean isChildSelectable(final int groupPosition, final int childPosition) {
+ return false;
+ }
+
+ private class GroupViewHolder {
+ private TextView time;
+ private TextView details;
+ private TextView concentration;
+ }
+
+ private class ChildViewHolder {
+ private TextView title;
+ private TextView details;
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseActivity.java
new file mode 100644
index 000000000..0021b1946
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseActivity.java
@@ -0,0 +1,222 @@
+/*
+ * 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.gls;
+
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileExpandableListActivity;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+
+public class GlucoseActivity extends BleProfileExpandableListActivity implements PopupMenu.OnMenuItemClickListener, GlucoseManagerCallbacks {
+ @SuppressWarnings("unused")
+ private static final String TAG = "GlucoseActivity";
+
+ private BaseExpandableListAdapter mAdapter;
+ private GlucoseManager mGlucoseManager;
+
+ private View mControlPanelStd;
+ private View mControlPanelAbort;
+ private TextView mUnitView;
+
+ @Override
+ protected void onCreateView(final Bundle savedInstanceState) {
+ // FEATURE_INDETERMINATE_PROGRESS notifies the system, that we are going to show indeterminate progress bar in the ActionBar (during device scan)
+ // requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); // <- Deprecated
+ setContentView(R.layout.activity_feature_gls);
+ setGUI();
+ }
+
+ private void setGUI() {
+ mUnitView = (TextView) findViewById(R.id.unit);
+ mControlPanelStd = findViewById(R.id.gls_control_std);
+ mControlPanelAbort = findViewById(R.id.gls_control_abort);
+
+ findViewById(R.id.action_last).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mGlucoseManager.getLastRecord();
+ }
+ });
+ findViewById(R.id.action_all).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mGlucoseManager.getAllRecords();
+ }
+ });
+ findViewById(R.id.action_abort).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mGlucoseManager.abort();
+ }
+ });
+
+ // create popup menu attached to the button More
+ findViewById(R.id.action_more).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ PopupMenu menu = new PopupMenu(GlucoseActivity.this, v);
+ menu.setOnMenuItemClickListener(GlucoseActivity.this);
+ MenuInflater inflater = menu.getMenuInflater();
+ inflater.inflate(R.menu.gls_more, menu.getMenu());
+ menu.show();
+ }
+ });
+
+ setListAdapter(mAdapter = new ExpandableRecordAdapter(this, mGlucoseManager));
+ }
+
+ @Override
+ protected BleManager initializeManager() {
+ GlucoseManager manager = mGlucoseManager = GlucoseManager.getGlucoseManager();
+ manager.setGattCallbacks(this);
+ return manager;
+ }
+
+ @Override
+ public boolean onMenuItemClick(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_refresh:
+ mGlucoseManager.refreshRecords();
+ break;
+ case R.id.action_first:
+ mGlucoseManager.getFirstRecord();
+ break;
+ case R.id.action_clear:
+ mGlucoseManager.clear();
+ break;
+ case R.id.action_delete_all:
+ mGlucoseManager.deleteAllRecords();
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ protected int getAboutTextId() {
+ return R.string.gls_about_text;
+ }
+
+ @Override
+ protected int getDefaultDeviceName() {
+ return R.string.gls_default_name;
+ }
+
+ @Override
+ protected UUID getFilterUUID() {
+ return GlucoseManager.GLS_SERVICE_UUID;
+ }
+
+ @Override
+ protected void setDefaultUI() {
+ mGlucoseManager.clear();
+ }
+
+ private void setOperationInProgress(final boolean progress) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ // setSupportProgressBarIndeterminateVisibility(progress);
+ mControlPanelStd.setVisibility(!progress ? View.VISIBLE : View.GONE);
+ mControlPanelAbort.setVisibility(progress ? View.VISIBLE : View.GONE);
+ }
+ });
+ }
+
+ @Override
+ public void onServicesDiscovered(boolean optionalServicesFound) {
+ // this may notify user or show some views
+ }
+
+ @Override
+ public void onGlucoseMeasurementNotificationEnabled() {
+ // this may notify user or show some views
+ }
+
+ @Override
+ public void onGlucoseMeasurementContextNotificationEnabled() {
+ // this may notify user or show some views
+ }
+
+ @Override
+ public void onRecordAccessControlPointIndicationsEnabled() {
+ // this may notify user or show some views
+ }
+
+ @Override
+ public void onOperationStarted() {
+ setOperationInProgress(true);
+ }
+
+ @Override
+ public void onOperationCompleted() {
+ setOperationInProgress(false);
+ }
+
+ @Override
+ public void onOperationAborted() {
+ setOperationInProgress(false);
+ }
+
+ @Override
+ public void onOperationNotSupported() {
+ setOperationInProgress(false);
+ showToast(R.string.gls_operation_not_supported);
+ }
+
+ @Override
+ public void onOperationFailed() {
+ setOperationInProgress(false);
+ showToast(R.string.gls_operation_failed);
+ }
+
+ @Override
+ public void onDatasetChanged() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final SparseArray records = mGlucoseManager.getRecords();
+ if (records.size() > 0) {
+ final int unit = records.valueAt(0).unit;
+ mUnitView.setVisibility(View.VISIBLE);
+ mUnitView.setText(unit == GlucoseRecord.UNIT_kgpl ? R.string.gls_unit_mgpdl : R.string.gls_unit_mmolpl);
+ } else
+ mUnitView.setVisibility(View.GONE);
+
+ mAdapter.notifyDataSetChanged();
+ }
+ });
+ }
+
+ @Override
+ public void onNumberOfRecordsRequested(final int value) {
+ showToast(getString(R.string.gls_progress, value));
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManager.java
new file mode 100644
index 000000000..a734e845d
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManager.java
@@ -0,0 +1,698 @@
+/*
+ * 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.gls;
+
+import java.util.Calendar;
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.SparseArray;
+
+@SuppressWarnings("unused")
+public class GlucoseManager implements BleManager {
+ private static final String TAG = "GlucoseManager";
+
+ private GlucoseManagerCallbacks mCallbacks;
+ private BluetoothGatt mBluetoothGatt;
+ private Context mContext;
+ private Handler mHandler;
+ private boolean mAbort;
+
+ private final SparseArray mRecords = new SparseArray<>();
+
+ public final static UUID GLS_SERVICE_UUID = UUID.fromString("00001808-0000-1000-8000-00805f9b34fb");
+ public final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb");
+ /** Glucose Measurement characteristic */
+ private final static UUID GM_CHARACTERISTIC = UUID.fromString("00002A18-0000-1000-8000-00805f9b34fb");
+ /** Glucose Measurement Context characteristic */
+ private final static UUID GM_CONTEXT_CHARACTERISTIC = UUID.fromString("00002A34-0000-1000-8000-00805f9b34fb");
+ /** Glucose Feature characteristic */
+ private final static UUID GF_CHARACTERISTIC = UUID.fromString("00002A51-0000-1000-8000-00805f9b34fb");
+ /** Record Access Control Point characteristic */
+ private final static UUID RACP_CHARACTERISTIC = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb");
+ /** Battery Level characteristic */
+ private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb");
+ /** Client configuration descriptor that will allow us to enable notifications and indications */
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+ private final static int OP_CODE_REPORT_STORED_RECORDS = 1;
+ private final static int OP_CODE_DELETE_STORED_RECORDS = 2;
+ private final static int OP_CODE_ABORT_OPERATION = 3;
+ private final static int OP_CODE_REPORT_NUMBER_OF_RECORDS = 4;
+ private final static int OP_CODE_NUMBER_OF_STORED_RECORDS_RESPONSE = 5;
+ private final static int OP_CODE_RESPONSE_CODE = 6;
+
+ private final static int OPERATOR_NULL = 0;
+ private final static int OPERATOR_ALL_RECORDS = 1;
+ private final static int OPERATOR_LESS_THEN_OR_EQUAL = 2;
+ private final static int OPERATOR_GREATER_THEN_OR_EQUAL = 3;
+ private final static int OPERATOR_WITHING_RANGE = 4;
+ private final static int OPERATOR_FIRST_RECORD = 5;
+ private final static int OPERATOR_LAST_RECORD = 6;
+
+ /**
+ * The filter type is used for range operators ({@link #OPERATOR_LESS_THEN_OR_EQUAL}, {@link #OPERATOR_GREATER_THEN_OR_EQUAL}, {@link #OPERATOR_WITHING_RANGE}.
+ * The syntax of the operand is: [Filter Type][Minimum][Maximum].
+ * This filter selects the records by the sequence number.
+ */
+ private final static int FILTER_TYPE_SEQUENCE_NUMBER = 1;
+ /**
+ * The filter type is used for range operators ({@link #OPERATOR_LESS_THEN_OR_EQUAL}, {@link #OPERATOR_GREATER_THEN_OR_EQUAL}, {@link #OPERATOR_WITHING_RANGE}.
+ * The syntax of the operand is: [Filter Type][Minimum][Maximum].
+ * This filter selects the records by the user facing time (base time + offset time).
+ */
+ private final static int FILTER_TYPE_USER_FACING_TIME = 2;
+
+ private final static int RESPONSE_SUCCESS = 1;
+ private final static int RESPONSE_OP_CODE_NOT_SUPPORTED = 2;
+ private final static int RESPONSE_INVALID_OPERATOR = 3;
+ private final static int RESPONSE_OPERATOR_NOT_SUPPORTED = 4;
+ private final static int RESPONSE_INVALID_OPERAND = 5;
+ private final static int RESPONSE_NO_RECORDS_FOUND = 6;
+ private final static int RESPONSE_ABORT_UNSUCCESSFUL = 7;
+ private final static int RESPONSE_PROCEDURE_NOT_COMPLETED = 8;
+ private final static int RESPONSE_OPERAND_NOT_SUPPORTED = 9;
+
+ private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
+ private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
+ private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor";
+ private final static String ERROR_WRITE_CHARACTERISTIC = "Error on writing characteristic";
+ private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information";
+
+ private BluetoothGattCharacteristic mGlucoseMeasurementCharacteristic;
+ private BluetoothGattCharacteristic mGlucoseFeatureCharacteristic;
+ private BluetoothGattCharacteristic mGlucoseMeasurementContextCharacteristic;
+ private BluetoothGattCharacteristic mRecordAccessControlPointCharacteristic;
+ private BluetoothGattCharacteristic mBatteryLevelCharacteristic;
+
+ private static GlucoseManager mInstance;
+
+ /**
+ * Returns the singleton implementation of GlucoseManager
+ */
+ public static GlucoseManager getGlucoseManager() {
+ if (mInstance == null)
+ mInstance = new GlucoseManager();
+ return mInstance;
+ }
+
+ /**
+ * Callbacks for activity {@link GlucoseActivity} that implements {@link GlucoseManagerCallbacks} interface activity use this method to register itself for receiving callbacks
+ */
+ @Override
+ public void setGattCallbacks(final GlucoseManagerCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ /**
+ * Returns all records as a sparse array where sequence number is the key.
+ *
+ * @return the records list
+ */
+ public SparseArray getRecords() {
+ return mRecords;
+ }
+
+ @Override
+ public void connect(final Context context, final BluetoothDevice device) {
+ if (mHandler == null)
+ mHandler = new Handler();
+ mContext = context;
+
+ // Register bonding broadcast receiver
+ final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ context.registerReceiver(mBondingBroadcastReceiver, filter);
+
+ mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
+ }
+
+ /**
+ * Disable HR notification first and then disconnect to HR device
+ */
+ @Override
+ public void disconnect() {
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ }
+ }
+
+ /**
+ * Clears the records list locally
+ */
+ public void clear() {
+ mRecords.clear();
+ mCallbacks.onDatasetChanged();
+ }
+
+ /**
+ * Sends the request to obtain the last (most recent) record from glucose device. The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access
+ * Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error.
+ */
+ public void getLastRecord() {
+ if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null)
+ return;
+
+ clear();
+ mCallbacks.onOperationStarted();
+
+ final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
+ setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_LAST_RECORD);
+ mBluetoothGatt.writeCharacteristic(characteristic);
+ }
+
+ /**
+ * Sends the request to obtain the first (oldest) record from glucose device. The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access Control
+ * Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error.
+ */
+ public void getFirstRecord() {
+ if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null)
+ return;
+
+ clear();
+ mCallbacks.onOperationStarted();
+
+ final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
+ setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_FIRST_RECORD);
+ mBluetoothGatt.writeCharacteristic(characteristic);
+ }
+
+ /**
+ * Sends the request to obtain all records from glucose device. Initially we want to notify him/her about the number of the records so the {@link #OP_CODE_REPORT_NUMBER_OF_RECORDS} is send. The
+ * data will be returned to Glucose Measurement characteristic as a notification followed by Record Access Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of
+ * error.
+ */
+ public void getAllRecords() {
+ if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null)
+ return;
+
+ clear();
+ mCallbacks.onOperationStarted();
+
+ final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
+ setOpCode(characteristic, OP_CODE_REPORT_NUMBER_OF_RECORDS, OPERATOR_ALL_RECORDS);
+ mBluetoothGatt.writeCharacteristic(characteristic);
+ }
+
+ /**
+ * Sends the request to obtain from the glucose device all records newer than the newest one from local storage. The data will be returned to Glucose Measurement characteristic as a notification
+ * followed by Record Access Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error.
+ *
+ * Refresh button will not download records older than the oldest in the local memory. F.e. if you have pressed Last and then Refresh, than it will try to get only newer records. However if there
+ * are no records, it will download all existing (using {@link #getAllRecords()}).
+ *
+ */
+ public void refreshRecords() {
+ if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null)
+ return;
+
+ if (mRecords.size() == 0) {
+ getAllRecords();
+ } else {
+ mCallbacks.onOperationStarted();
+
+ // obtain the last sequence number
+ final int sequenceNumber = mRecords.keyAt(mRecords.size() - 1) + 1;
+
+ final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
+ setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_GREATER_THEN_OR_EQUAL, sequenceNumber);
+ mBluetoothGatt.writeCharacteristic(characteristic);
+ // Info:
+ // Operators OPERATOR_LESS_THEN_OR_EQUAL and OPERATOR_RANGE are not supported by Nordic Semiconductor Glucose Service in SDK 4.4.2.
+ }
+ }
+
+ /**
+ * Sends abort operation signal to the device
+ */
+ public void abort() {
+ if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null)
+ return;
+
+ mAbort = true;
+ final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
+ setOpCode(characteristic, OP_CODE_ABORT_OPERATION, OPERATOR_NULL);
+ mBluetoothGatt.writeCharacteristic(characteristic);
+ }
+
+ /**
+ * Sends the request to delete all data from the device. A Record Access Control Point indication with status code ({@link #RESPONSE_SUCCESS} (or other in case of error) will be send.
+ *
+ * @FIXME This method is not supported by Nordic Semiconductor Glucose Service in SDK 4.4.2.
+ */
+ public void deleteAllRecords() {
+ if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null)
+ return;
+
+ clear();
+ mCallbacks.onOperationStarted();
+
+ final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
+ setOpCode(characteristic, OP_CODE_DELETE_STORED_RECORDS, OPERATOR_ALL_RECORDS);
+ mBluetoothGatt.writeCharacteristic(characteristic);
+ }
+
+ private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+ final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
+
+ DebugLogger.d(TAG, "Bond state changed for: " + device.getAddress() + " new state: " + bondState + " previous: " + previousBondState);
+
+ // skip other devices
+ if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
+ return;
+
+ if (bondState == BluetoothDevice.BOND_BONDING) {
+ mCallbacks.onBondingRequired();
+ } else if (bondState == BluetoothDevice.BOND_BONDED) {
+ // We've read Battery Level, now let'so enable notifications and indication
+ enableGlucoseMeasurementNotification(mBluetoothGatt);
+ mCallbacks.onBonded();
+ }
+ }
+ };
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc
+ */
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ mCallbacks.onDeviceConnected();
+ // start discovering services
+ gatt.discoverServices();
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ mCallbacks.onDeviceDisconnected();
+ gatt.close();
+ }
+ } else {
+ DebugLogger.e(TAG, "onConnectionStateChange error " + status);
+ mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ for (BluetoothGattService service : gatt.getServices()) {
+ if (GLS_SERVICE_UUID.equals(service.getUuid())) {
+ mGlucoseMeasurementCharacteristic = service.getCharacteristic(GM_CHARACTERISTIC);
+ mGlucoseMeasurementContextCharacteristic = service.getCharacteristic(GM_CONTEXT_CHARACTERISTIC);
+ mGlucoseFeatureCharacteristic = service.getCharacteristic(GF_CHARACTERISTIC);
+ mRecordAccessControlPointCharacteristic = service.getCharacteristic(RACP_CHARACTERISTIC);
+ } else if (BATTERY_SERVICE.equals(service.getUuid())) {
+ mBatteryLevelCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC);
+ }
+ }
+ // Validate the device for required characteristics
+ if (mGlucoseMeasurementCharacteristic == null || mRecordAccessControlPointCharacteristic == null) {
+ mCallbacks.onDeviceNotSupported();
+ gatt.disconnect();
+ return;
+ }
+ mCallbacks.onServicesDiscovered(mGlucoseMeasurementContextCharacteristic != null);
+
+ // We have discovered services, let's start notifications and indications, one by one: read battery, enable GM, GMP (if exists) and RACP
+ if (mBatteryLevelCharacteristic != null) {
+ readBatteryLevel(gatt);
+ } else {
+ // this characteristic is mandatory
+ enableGlucoseMeasurementNotification(gatt);
+ }
+ } else {
+ DebugLogger.e(TAG, "onServicesDiscovered error " + status);
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (BATTERY_LEVEL_CHARACTERISTIC.equals(characteristic.getUuid())) {
+ final int batteryValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+ mCallbacks.onBatteryValueReceived(batteryValue);
+
+ // We've read Battery Level, now let'so enable ICP notifications or BPM indications
+ enableGlucoseMeasurementNotification(gatt);
+ }
+ } else {
+ DebugLogger.e(TAG, "onCharacteristicRead error " + status);
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (GM_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid())) {
+ mCallbacks.onGlucoseMeasurementNotificationEnabled();
+
+ if (mGlucoseMeasurementContextCharacteristic != null) {
+ enableGlucoseMeasurementContextNotification(gatt);
+ } else {
+ enableRecordAccessControlPointIndication(gatt);
+ }
+ }
+
+ if (GM_CONTEXT_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid())) {
+ mCallbacks.onGlucoseMeasurementContextNotificationEnabled();
+ enableRecordAccessControlPointIndication(gatt);
+ }
+
+ if (RACP_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid())) {
+ mCallbacks.onRecordAccessControlPointIndicationsEnabled();
+ }
+ } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+ if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
+ DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
+ mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
+ }
+ } else {
+ DebugLogger.e(TAG, "onDescriptorWrite error " + status);
+ mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ final UUID uuid = characteristic.getUuid();
+ if (GM_CHARACTERISTIC.equals(uuid)) {
+ int offset = 0;
+ final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ offset += 1;
+
+ final boolean timeOffsetPresent = (flags & 0x01) > 0;
+ final boolean typeAndLocationPresent = (flags & 0x02) > 0;
+ final int concentrationUnit = (flags & 0x04) > 0 ? GlucoseRecord.UNIT_molpl : GlucoseRecord.UNIT_kgpl;
+ final boolean sensorStatusAnnunciationPresent = (flags & 0x08) > 0;
+ final boolean contextInfoFollows = (flags & 0x10) > 0;
+
+ // create and fill the new record
+ final GlucoseRecord record = new GlucoseRecord();
+ record.sequenceNumber = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+ offset += 2;
+
+ final int year = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+ final int month = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2) + 1; // months are 1-based
+ final int day = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 3);
+ final int hours = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 4);
+ final int minutes = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 5);
+ final int seconds = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 6);
+ offset += 7;
+
+ final Calendar calendar = Calendar.getInstance();
+ calendar.set(year, month, day, hours, minutes, seconds);
+ record.time = calendar;
+
+ if (timeOffsetPresent) {
+ // time offset is ignored in the current release
+ record.timeOffset = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, offset);
+ offset += 2;
+ }
+
+ if (typeAndLocationPresent) {
+ record.glucoseConcentration = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
+ record.unit = concentrationUnit;
+ final int typeAndLocation = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2);
+ record.type = (typeAndLocation & 0xF0) >> 4; // TODO this way or around?
+ record.sampleLocation = (typeAndLocation & 0x0F);
+ offset += 3;
+ }
+
+ if (sensorStatusAnnunciationPresent) {
+ record.status = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+ }
+ // This allows you to check other values that are not provided by the Nordic Semiconductor's Glucose Service in SDK 4.4.2.
+ // record.status = 0x1A;
+ // record.context = new GlucoseRecord.MeasurementContext();
+ // record.context.carbohydrateId = 1;
+ // record.context.carbohydrateUnits = 0.23f;
+ // record.context.meal = 2;
+ // record.context.tester = 2;
+ // record.context.health = 4;
+ // the following values are not implemented yet (see ExpandableRecordAdapter#getChildrenCount() and #getChild(...)
+ // record.context.exerciseDuration = 3600;
+ // record.context.exerciseIntensity = 45;
+ // record.context.medicationId = 3;
+ // record.context.medicationQuantity = 0.03f;
+ // record.context.medicationUnit = GlucoseRecord.MeasurementContext.UNIT_kg;
+ // record.context.HbA1c = 213.3f;
+
+ // data set modifications must be done in UI thread
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // insert the new record to storage
+ mRecords.put(record.sequenceNumber, record);
+
+ // if there is no context information following the measurement data, notify callback about the new record
+ if (!contextInfoFollows)
+ mCallbacks.onDatasetChanged();
+ }
+ });
+ } else if (GM_CONTEXT_CHARACTERISTIC.equals(uuid)) {
+ int offset = 0;
+ final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ offset += 1;
+
+ final boolean carbohydratePresent = (flags & 0x01) > 0;
+ final boolean mealPresent = (flags & 0x02) > 0;
+ final boolean testerHealthPresent = (flags & 0x04) > 0;
+ final boolean exercisePresent = (flags & 0x08) > 0;
+ final boolean medicationPresent = (flags & 0x10) > 0;
+ final int medicationUnit = (flags & 0x20) > 0 ? GlucoseRecord.MeasurementContext.UNIT_l : GlucoseRecord.MeasurementContext.UNIT_kg;
+ final boolean hbA1cPresent = (flags & 0x40) > 0;
+ final boolean moreFlagsPresent = (flags & 0x80) > 0;
+
+ final int sequenceNumber = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+ offset += 2;
+
+ final GlucoseRecord record = mRecords.get(sequenceNumber);
+ if (record == null) {
+ DebugLogger.w(TAG, "Context information with unknown sequence number: " + sequenceNumber);
+ return;
+ }
+
+ final GlucoseRecord.MeasurementContext context = new GlucoseRecord.MeasurementContext();
+ record.context = context;
+
+ if (moreFlagsPresent)
+ offset += 1;
+
+ if (carbohydratePresent) {
+ context.carbohydrateId = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ context.carbohydrateUnits = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 1);
+ offset += 3;
+ }
+
+ if (mealPresent) {
+ context.meal = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ offset += 1;
+ }
+
+ if (testerHealthPresent) {
+ final int testerHealth = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ context.tester = (testerHealth & 0xF0) >> 4;
+ context.health = (testerHealth & 0x0F);
+ offset += 1;
+ }
+
+ if (exercisePresent) {
+ context.exerciseDuration = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+ context.exerciseIntensity = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2);
+ offset += 3;
+ }
+
+ if (medicationPresent) {
+ context.medicationId = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ context.medicationQuantity = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 1);
+ context.medicationUnit = medicationUnit;
+ offset += 3;
+ }
+
+ if (hbA1cPresent) {
+ context.HbA1c = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
+ }
+
+ // notify callback about the new record
+ mCallbacks.onDatasetChanged();
+ } else { // Record Access Control Point characteristic
+ int offset = 0;
+ final int opCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ offset += 2; // skip the operator
+
+ if (opCode == OP_CODE_NUMBER_OF_STORED_RECORDS_RESPONSE) {
+ // We've obtained the number of all records
+ final int number = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+
+ mCallbacks.onNumberOfRecordsRequested(number);
+
+ // Request the records
+ final BluetoothGattCharacteristic racpCharacteristic = mRecordAccessControlPointCharacteristic;
+ setOpCode(racpCharacteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_ALL_RECORDS);
+ mBluetoothGatt.writeCharacteristic(racpCharacteristic);
+ } else if (opCode == OP_CODE_RESPONSE_CODE) {
+ final int requestedOpCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ final int responseCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 1);
+ DebugLogger.d(TAG, "Response result for: " + requestedOpCode + " is: " + responseCode);
+
+ switch (responseCode) {
+ case RESPONSE_SUCCESS:
+ if (!mAbort)
+ mCallbacks.onOperationCompleted();
+ else
+ mCallbacks.onOperationAborted();
+ break;
+ case RESPONSE_NO_RECORDS_FOUND:
+ mCallbacks.onOperationCompleted();
+ break;
+ case RESPONSE_OP_CODE_NOT_SUPPORTED:
+ mCallbacks.onOperationNotSupported();
+ break;
+ case RESPONSE_PROCEDURE_NOT_COMPLETED:
+ case RESPONSE_ABORT_UNSUCCESSFUL:
+ default:
+ mCallbacks.onOperationFailed();
+ break;
+ }
+ mAbort = false;
+ }
+ }
+ }
+
+ /**
+ * Reads battery level on the device
+ */
+ private void readBatteryLevel(final BluetoothGatt gatt) {
+ DebugLogger.d(TAG, "readBatteryLevel()");
+ gatt.readCharacteristic(mBatteryLevelCharacteristic);
+ }
+ };
+
+ /**
+ * Enabling notification on Glucose Measurement Characteristic
+ */
+ private void enableGlucoseMeasurementNotification(final BluetoothGatt gatt) {
+ DebugLogger.d(TAG, "enableGlucoseMeasurementNotification()");
+ gatt.setCharacteristicNotification(mGlucoseMeasurementCharacteristic, true);
+ final BluetoothGattDescriptor descriptor = mGlucoseMeasurementCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ }
+
+ /**
+ * Enabling notification on Glucose Measurement Context Characteristic. This characteristic is optional
+ */
+ private void enableGlucoseMeasurementContextNotification(final BluetoothGatt gatt) {
+ DebugLogger.d(TAG, "enableGlucoseMeasurementContextNotification()");
+ gatt.setCharacteristicNotification(mGlucoseMeasurementContextCharacteristic, true);
+ final BluetoothGattDescriptor descriptor = mGlucoseMeasurementContextCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ }
+
+ /**
+ * Enabling indications on Record Access Control Point Characteristic
+ */
+ private void enableRecordAccessControlPointIndication(final BluetoothGatt gatt) {
+ DebugLogger.d(TAG, "enableGlucoseMeasurementContextNotification()");
+ gatt.setCharacteristicNotification(mRecordAccessControlPointCharacteristic, true);
+ final BluetoothGattDescriptor descriptor = mRecordAccessControlPointCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ }
+
+ /**
+ * Writes given operation parameters to the characteristic
+ *
+ * @param characteristic
+ * the characteristic to write. This must be the Record Access Control Point characteristic
+ * @param opCode
+ * the operation code
+ * @param operator
+ * the operator (see {@link #OPERATOR_NULL} and others
+ * @param params
+ * optional parameters (one for >=, <=, two for the range, none for other operators)
+ */
+ private void setOpCode(final BluetoothGattCharacteristic characteristic, final int opCode, final int operator, final Integer... params) {
+ final int size = 2 + ((params.length > 0) ? 1 : 0) + params.length * 2; // 1 byte for opCode, 1 for operator, 1 for filter type (if parameters exists) and 2 for each parameter
+ characteristic.setValue(new byte[size]);
+
+ // write the operation code
+ int offset = 0;
+ characteristic.setValue(opCode, BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ offset += 1;
+
+ // write the operator. This is always present but may be equal to OPERATOR_NULL
+ characteristic.setValue(operator, BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ offset += 1;
+
+ // if parameters exists, append them. Parameters should be sorted from minimum to maximum. Currently only one or two params are allowed
+ if (params.length > 0) {
+ // our implementation use only sequence number as a filer type
+ characteristic.setValue(FILTER_TYPE_SEQUENCE_NUMBER, BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ offset += 1;
+
+ for (final Integer i : params) {
+ characteristic.setValue(i, BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+ offset += 2;
+ }
+ }
+ }
+
+ @Override
+ public void closeBluetoothGatt() {
+ try {
+ mContext.unregisterReceiver(mBondingBroadcastReceiver);
+ } catch (Exception e) {
+ // the receiver must have been not registered or unregistered before
+ }
+
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mRecords.clear();
+ mGlucoseMeasurementCharacteristic = null;
+ mGlucoseMeasurementContextCharacteristic = null;
+ mGlucoseFeatureCharacteristic = null;
+ mRecordAccessControlPointCharacteristic = null;
+ mBatteryLevelCharacteristic = null;
+ mBluetoothGatt = null;
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManagerCallbacks.java
new file mode 100644
index 000000000..83a2a3d6f
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManagerCallbacks.java
@@ -0,0 +1,49 @@
+/*
+ * 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.gls;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
+
+public interface GlucoseManagerCallbacks extends BleManagerCallbacks {
+ public static final int UNIT_mmHG = 0;
+ public static final int UNIT_kPa = 1;
+
+ public void onGlucoseMeasurementNotificationEnabled();
+
+ public void onGlucoseMeasurementContextNotificationEnabled();
+
+ public void onRecordAccessControlPointIndicationsEnabled();
+
+ public void onOperationStarted();
+
+ public void onOperationCompleted();
+
+ public void onOperationFailed();
+
+ public void onOperationAborted();
+
+ public void onOperationNotSupported();
+
+ public void onDatasetChanged();
+
+ public void onNumberOfRecordsRequested(final int value);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseRecord.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseRecord.java
new file mode 100644
index 000000000..f83c17317
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseRecord.java
@@ -0,0 +1,118 @@
+/*
+ * 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.gls;
+
+import java.util.Calendar;
+
+public class GlucoseRecord {
+ public static final int UNIT_kgpl = 0;
+ public static final int UNIT_molpl = 1;
+
+ /** Record sequence number */
+ protected int sequenceNumber;
+ /** The base time of the measurement */
+ protected Calendar time;
+ /** Time offset of the record */
+ protected int timeOffset;
+ /** The glucose concentration. 0 if not present */
+ protected float glucoseConcentration;
+ /** Concentration unit. One of the following: {@link GlucoseRecord#UNIT_kgpl}, {@link GlucoseRecord#UNIT_molpl} */
+ protected int unit;
+ /** The type of the record. 0 if not present */
+ protected int type;
+ /** The sample location. 0 if unknown */
+ protected int sampleLocation;
+ /** Sensor status annunciation flags. 0 if not present */
+ protected int status;
+
+ protected MeasurementContext context;
+
+ public static class MeasurementContext {
+ public static final int UNIT_kg = 0;
+ public static final int UNIT_l = 1;
+
+ /**
+ * One of the following:
+ * 0 Not present
+ * 1 Breakfast
+ * 2 Lunch
+ * 3 Dinner
+ * 4 Snack
+ * 5 Drink
+ * 6 Supper
+ * 7 Brunch
+ */
+ protected int carbohydrateId;
+ /** Number of kilograms of carbohydrate */
+ protected float carbohydrateUnits;
+ /**
+ * One of the following:
+ * 0 Not present
+ * 1 Preprandial (before meal)
+ * 2 Postprandial (after meal)
+ * 3 Fasting
+ * 4 Casual (snacks, drinks, etc.)
+ * 5 Bedtime
+ */
+ protected int meal;
+ /**
+ * One of the following:
+ * 0 Not present
+ * 1 Self
+ * 2 Health Care Professional
+ * 3 Lab test
+ * 15 Tester value not available
+ */
+ protected int tester;
+ /**
+ * One of the following:
+ * 0 Not present
+ * 1 Minor health issues
+ * 2 Major health issues
+ * 3 During menses
+ * 4 Under stress
+ * 5 No health issues
+ * 15 Tester value not available
+ */
+ protected int health;
+ /** Exercise duration in seconds. 0 if not present */
+ protected int exerciseDuration;
+ /** Exercise intensity in percent. 0 if not present */
+ protected int exerciseIntensity;
+ /**
+ * One of the following:
+ * 0 Not present
+ * 1 Rapid acting insulin
+ * 2 Short acting insulin
+ * 3 Intermediate acting insulin
+ * 4 Long acting insulin
+ * 5 Pre-mixed insulin
+ */
+ protected int medicationId;
+ /** Quantity of medication. See {@link #medicationUnit} for the unit. */
+ protected float medicationQuantity;
+ /** One of the following: {@link GlucoseRecord.MeasurementContext#UNIT_kg}, {@link GlucoseRecord.MeasurementContext#UNIT_l}. */
+ protected int medicationUnit;
+ /** HbA1c value. 0 if not present */
+ protected float HbA1c;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java
new file mode 100644
index 000000000..7e987e297
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java
@@ -0,0 +1,235 @@
+/*
+ * 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.hrs;
+
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileActivity;
+
+import org.achartengine.GraphicalView;
+
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * HRSActivity is the main Heart rate activity. It implements HRSManagerCallbacks to receive callbacks from HRSManager class. The activity supports portrait and landscape orientations. The activity
+ * uses external library AChartEngine to show real time graph of HR values.
+ */
+public class HRSActivity extends BleProfileActivity implements HRSManagerCallbacks {
+ @SuppressWarnings("unused")
+ private final String TAG = "HRSActivity";
+
+ private static final String GRAPH_STATUS = "graph_status";
+ private static final String GRAPH_COUNTER = "graph_counter";
+ private static final String HR_VALUE = "hr_value";
+
+ private final int MAX_HR_VALUE = 65535;
+ private final int MIN_POSITIVE_VALUE = 0;
+ private final int REFRESH_INTERVAL = 1000; // 1 second interval
+
+ private Handler mHandler = new Handler();
+
+ private boolean isGraphInProgress = false;
+
+ private GraphicalView mGraphView;
+ private LineGraphView mLineGraph;
+ private TextView mHRSValue, mHRSPosition;
+
+ private int mHrmValue = 0;
+ private int mCounter = 0;
+
+ @Override
+ protected void onCreateView(Bundle savedInstanceState) {
+ setContentView(R.layout.activity_feature_hrs);
+ setGUI();
+ }
+
+ private void setGUI() {
+ mLineGraph = LineGraphView.getLineGraphView();
+ mHRSValue = (TextView) findViewById(R.id.text_hrs_value);
+ mHRSPosition = (TextView) findViewById(R.id.text_hrs_position);
+ showGraph();
+ }
+
+ private void showGraph() {
+ mGraphView = mLineGraph.getView(this);
+ ViewGroup layout = (ViewGroup) findViewById(R.id.graph_hrs);
+ layout.addView(mGraphView);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ isGraphInProgress = savedInstanceState.getBoolean(GRAPH_STATUS);
+ mCounter = savedInstanceState.getInt(GRAPH_COUNTER);
+ mHrmValue = savedInstanceState.getInt(HR_VALUE);
+
+ if (isGraphInProgress)
+ startShowGraph();
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putBoolean(GRAPH_STATUS, isGraphInProgress);
+ outState.putInt(GRAPH_COUNTER, mCounter);
+ outState.putInt(HR_VALUE, mHrmValue);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ stopShowGraph();
+ }
+
+ @Override
+ protected int getAboutTextId() {
+ return R.string.hrs_about_text;
+ }
+
+ @Override
+ protected int getDefaultDeviceName() {
+ return R.string.hrs_default_name;
+ }
+
+ @Override
+ protected UUID getFilterUUID() {
+ return HRSManager.HR_SERVICE_UUID;
+ }
+
+ private void updateGraph(final int hrmValue) {
+ mCounter++;
+ mLineGraph.addValue(new Point(mCounter, hrmValue));
+ mGraphView.repaint();
+ }
+
+ private Runnable mRepeatTask = new Runnable() {
+ @Override
+ public void run() {
+ if (mHrmValue > 0)
+ updateGraph(mHrmValue);
+ if (isGraphInProgress)
+ mHandler.postDelayed(mRepeatTask, REFRESH_INTERVAL);
+ }
+ };
+
+ void startShowGraph() {
+ isGraphInProgress = true;
+ mRepeatTask.run();
+ }
+
+ void stopShowGraph() {
+ isGraphInProgress = false;
+ mHandler.removeCallbacks(mRepeatTask);
+ }
+
+ @Override
+ protected BleManager initializeManager() {
+ HRSManager manager = HRSManager.getInstance(this);
+ manager.setGattCallbacks(this);
+ return manager;
+ }
+
+ private void setHRSValueOnView(final int value) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (value >= MIN_POSITIVE_VALUE && value <= MAX_HR_VALUE) {
+ mHRSValue.setText(Integer.toString(value));
+ } else {
+ mHRSValue.setText(R.string.not_available_value);
+ }
+ }
+ });
+ }
+
+ private void setHRSPositionOnView(final String position) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (position != null) {
+ mHRSPosition.setText(position);
+ } else {
+ mHRSPosition.setText(R.string.not_available);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onServicesDiscovered(boolean optionalServicesFound) {
+ // this may notify user or show some views
+ }
+
+ @Override
+ public void onHRSensorPositionFound(String position) {
+ setHRSPositionOnView(position);
+ }
+
+ @Override
+ public void onHRNotificationEnabled() {
+ startShowGraph();
+ }
+
+ @Override
+ public void onHRValueReceived(int value) {
+ mHrmValue = value;
+ setHRSValueOnView(mHrmValue);
+ }
+
+ @Override
+ public void onDeviceDisconnected() {
+ super.onDeviceDisconnected();
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mHRSValue.setText(R.string.not_available_value);
+ mHRSPosition.setText(R.string.not_available);
+ stopShowGraph();
+ }
+ });
+ }
+
+ @Override
+ protected void setDefaultUI() {
+ mHRSValue.setText(R.string.not_available_value);
+ mHRSPosition.setText(R.string.not_available);
+ clearGraph();
+ }
+
+ private void clearGraph() {
+ mLineGraph.clearGraph();
+ mGraphView.repaint();
+ mCounter = 0;
+ mHrmValue = 0;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java
new file mode 100644
index 000000000..4cf393e5b
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java
@@ -0,0 +1,251 @@
+/*
+ * 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.hrs;
+
+import java.util.List;
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+/**
+ * HRSManager class performs BluetoothGatt operations for connection, service discovery, enabling notification and reading characteristics. All operations required to connect to device with BLE HR
+ * Service and reading heart rate values are performed here. HRSActivity implements HRSManagerCallbacks in order to receive callbacks of BluetoothGatt operations
+ */
+public class HRSManager implements BleManager {
+ private final String TAG = "HRSManager";
+ private HRSManagerCallbacks mCallbacks;
+ private BluetoothGatt mBluetoothGatt;
+ private Context mContext;
+
+ public final static UUID HR_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb");
+ private static final UUID HR_SENSOR_LOCATION_CHARACTERISTIC_UUID = UUID.fromString("00002A38-0000-1000-8000-00805f9b34fb");
+
+ private static final UUID HR_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb");
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+ private final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb");
+ private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb");
+
+ private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
+ private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
+ private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor";
+ private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic";
+
+ private BluetoothGattCharacteristic mHRCharacteristic, mHRLocationCharacteristic, mBatteryCharacteristic;
+ private static final int FIRST_BITMASK = 0x01;
+
+ private static HRSManager managerInstance = null;
+
+ /**
+ * singleton implementation of HRSManager class
+ */
+ public static synchronized HRSManager getInstance(Context context) {
+ if (managerInstance == null) {
+ managerInstance = new HRSManager();
+ }
+ managerInstance.mContext = context;
+ return managerInstance;
+ }
+
+ /**
+ * callbacks for activity {HRSActivity} that implements HRSManagerCallbacks interface activity use this method to register itself for receiving callbacks
+ */
+ @Override
+ public void setGattCallbacks(HRSManagerCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void connect(Context context, BluetoothDevice device) {
+ DebugLogger.d(TAG, "Connecting to device...");
+ mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
+ }
+
+ /**
+ * Disable HR notification first and then disconnect to HR device
+ */
+ @Override
+ public void disconnect() {
+ DebugLogger.d(TAG, "Disconnecting device...");
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ }
+ }
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc
+ */
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ DebugLogger.d(TAG, "Device connected");
+ mBluetoothGatt.discoverServices();
+ //This will send callback to HRSActivity when device get connected
+ mCallbacks.onDeviceConnected();
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ DebugLogger.d(TAG, "Device disconnected");
+ //This will send callback to HRSActivity when device get disconnected
+ mCallbacks.onDeviceDisconnected();
+ }
+ } else {
+ mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ List services = gatt.getServices();
+ for (BluetoothGattService service : services) {
+ if (service.getUuid().equals(HR_SERVICE_UUID)) {
+ mHRCharacteristic = service.getCharacteristic(HR_CHARACTERISTIC_UUID);
+ mHRLocationCharacteristic = service.getCharacteristic(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID);
+ } else if (service.getUuid().equals(BATTERY_SERVICE)) {
+ mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC);
+ }
+ }
+ if (mHRCharacteristic != null) {
+ //This will send callback to HRSActivity when HR Service is found in device
+ mCallbacks.onServicesDiscovered(false);
+ readHRSensorLocation();
+ } else {
+ mCallbacks.onDeviceNotSupported();
+ gatt.disconnect();
+ }
+ } else {
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (characteristic.getUuid().equals(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID)) {
+ final String sensorPosition = getBodySensorPosition(characteristic.getValue()[0]);
+ //This will send callback to HRSActivity when HR sensor position on body is found in HR device
+ mCallbacks.onHRSensorPositionFound(sensorPosition);
+
+ if (mBatteryCharacteristic != null) {
+ readBatteryLevel();
+ } else {
+ enableHRNotification();
+ }
+ }
+ if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC)) {
+ int batteryValue = characteristic.getValue()[0];
+ //This will send callback to HRSActivity when Battery value is received from HR device
+ mCallbacks.onBatteryValueReceived(batteryValue);
+
+ enableHRNotification();
+ }
+ } else {
+ mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ int hrValue;
+ //This will check if HR value is in 8 bits or 16 bits.
+ if (characteristic.getUuid().equals(HR_CHARACTERISTIC_UUID)) {
+ if (isHeartRateInUINT16(characteristic.getValue()[0])) {
+ hrValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1);
+ } else {
+ hrValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
+ }
+ //This will send callback to HRSActivity when new HR value is received from HR device
+ mCallbacks.onHRValueReceived(hrValue);
+ }
+ }
+
+ @Override
+ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ //This will send callback to HRSActivity when HR notification is enabled
+ mCallbacks.onHRNotificationEnabled();
+ } else {
+ mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status);
+ }
+ }
+ };
+
+ private void readBatteryLevel() {
+ if (mBatteryCharacteristic != null) {
+ mBluetoothGatt.readCharacteristic(mBatteryCharacteristic);
+ }
+ }
+
+ private void readHRSensorLocation() {
+ if (mHRLocationCharacteristic != null) {
+ mBluetoothGatt.readCharacteristic(mHRLocationCharacteristic);
+ }
+ }
+
+ /**
+ * This method will decode and return Heart rate sensor position on body
+ */
+ private String getBodySensorPosition(byte bodySensorPositionValue) {
+ String[] locations = mContext.getResources().getStringArray(R.array.hrs_locations);
+ if (bodySensorPositionValue > locations.length)
+ return mContext.getString(R.string.hrs_location_other);
+ return locations[bodySensorPositionValue];
+ }
+
+ /**
+ * This method will check if Heart rate value is in 8 bits or 16 bits
+ */
+ private boolean isHeartRateInUINT16(byte value) {
+ return ((value & FIRST_BITMASK) != 0);
+ }
+
+ /**
+ * Enabling notification on Heart Rate Characteristic
+ */
+ private void enableHRNotification() {
+ DebugLogger.d(TAG, "Enabling heart rate notifications");
+ mBluetoothGatt.setCharacteristicNotification(mHRCharacteristic, true);
+ BluetoothGattDescriptor descriptor = mHRCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ mBluetoothGatt.writeDescriptor(descriptor);
+ }
+
+ @Override
+ public void closeBluetoothGatt() {
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java
new file mode 100644
index 000000000..ab083e6e1
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java
@@ -0,0 +1,48 @@
+/*
+ * 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.hrs;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
+
+public interface HRSManagerCallbacks extends BleManagerCallbacks {
+
+ /**
+ * Called when Heart Rate notifications has been enabled
+ */
+ public void onHRNotificationEnabled();
+
+ /**
+ * Called when the sensor position information has been obtained from the sensor
+ *
+ * @param position
+ * the sensor position
+ */
+ public void onHRSensorPositionFound(String position);
+
+ /**
+ * Called when new Heart Rate value has been obtained from the sensor
+ *
+ * @param value
+ * the new value
+ */
+ public void onHRValueReceived(int value);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/LineGraphView.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/LineGraphView.java
new file mode 100644
index 000000000..c22082343
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/LineGraphView.java
@@ -0,0 +1,121 @@
+/*
+ * 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.hrs;
+
+import org.achartengine.ChartFactory;
+import org.achartengine.GraphicalView;
+import org.achartengine.chart.PointStyle;
+import org.achartengine.model.TimeSeries;
+import org.achartengine.model.XYMultipleSeriesDataset;
+import org.achartengine.renderer.XYMultipleSeriesRenderer;
+import org.achartengine.renderer.XYSeriesRenderer;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Paint.Align;
+import android.graphics.Point;
+
+/**
+ * This class uses external library AChartEngine to show dynamic real time line graph for HR values
+ */
+public class LineGraphView {
+ //TimeSeries will hold the data in x,y format for single chart
+ private TimeSeries mSeries = new TimeSeries("Heart Rate");
+ //XYMultipleSeriesDataset will contain all the TimeSeries
+ private XYMultipleSeriesDataset mDataset = new XYMultipleSeriesDataset();
+ //XYMultipleSeriesRenderer will contain all XYSeriesRenderer and it can be used to set the properties of whole Graph
+ private XYMultipleSeriesRenderer mMultiRenderer = new XYMultipleSeriesRenderer();
+ private static LineGraphView mInstance = null;
+
+ /**
+ * singleton implementation of LineGraphView class
+ */
+ public static synchronized LineGraphView getLineGraphView() {
+ if (mInstance == null) {
+ mInstance = new LineGraphView();
+ }
+ return mInstance;
+ }
+
+ /**
+ * This constructor will set some properties of single chart and some properties of whole graph
+ */
+ public LineGraphView() {
+ //add single line chart mSeries
+ mDataset.addSeries(mSeries);
+
+ //XYSeriesRenderer is used to set the properties like chart color, style of each point, etc. of single chart
+ final XYSeriesRenderer seriesRenderer = new XYSeriesRenderer();
+ //set line chart color to Black
+ seriesRenderer.setColor(Color.BLACK);
+ //set line chart style to square points
+ seriesRenderer.setPointStyle(PointStyle.SQUARE);
+ seriesRenderer.setFillPoints(true);
+
+ final XYMultipleSeriesRenderer renderer = mMultiRenderer;
+ //set whole graph background color to transparent color
+ renderer.setBackgroundColor(Color.TRANSPARENT);
+ renderer.setMargins(new int[] { 50, 65, 40, 5 }); // top, left, bottom, right
+ renderer.setMarginsColor(Color.argb(0x00, 0x01, 0x01, 0x01));
+ renderer.setAxesColor(Color.BLACK);
+ renderer.setAxisTitleTextSize(24);
+ renderer.setShowGrid(true);
+ renderer.setGridColor(Color.LTGRAY);
+ renderer.setLabelsColor(Color.BLACK);
+ renderer.setYLabelsColor(0, Color.DKGRAY);
+ renderer.setYLabelsAlign(Align.RIGHT);
+ renderer.setYLabelsPadding(4.0f);
+ renderer.setXLabelsColor(Color.DKGRAY);
+ renderer.setLabelsTextSize(20);
+ renderer.setLegendTextSize(20);
+ //Disable zoom
+ renderer.setPanEnabled(false, false);
+ renderer.setZoomEnabled(false, false);
+ //set title to x-axis and y-axis
+ renderer.setXTitle(" Time (seconds)");
+ renderer.setYTitle(" BPM");
+ renderer.addSeriesRenderer(seriesRenderer);
+ }
+
+ /**
+ * return graph view to activity
+ */
+ public GraphicalView getView(Context context) {
+ final GraphicalView graphView = ChartFactory.getLineChartView(context, mDataset, mMultiRenderer);
+ return graphView;
+ }
+
+ /**
+ * add new x,y value to chart
+ */
+ public void addValue(Point p) {
+ mSeries.add(p.x, p.y);
+ }
+
+ /**
+ * clear all previous values of chart
+ */
+ public void clearGraph() {
+ mSeries.clear();
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSActivity.java
new file mode 100644
index 000000000..3a9c0dd99
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSActivity.java
@@ -0,0 +1,132 @@
+/*
+ * 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.hts;
+
+import java.text.DecimalFormat;
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.widget.TextView;
+
+/**
+ * HTSActivity is the main Health Thermometer activity. It implements {@link HTSManagerCallbacks} to receive callbacks from {@link HTSManager} class. The activity supports portrait and landscape
+ * orientations.
+ */
+public class HTSActivity extends BleProfileServiceReadyActivity {
+ @SuppressWarnings("unused")
+ private final String TAG = "HTSActivity";
+
+ private TextView mHTSValue;
+
+ @Override
+ protected void onCreateView(Bundle savedInstanceState) {
+ setContentView(R.layout.activity_feature_hts);
+ setGUI();
+ }
+
+ @Override
+ protected void onInitialize(final Bundle savedInstanceState) {
+ LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, makeIntentFilter());
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);
+ }
+
+ private void setGUI() {
+ mHTSValue = (TextView) findViewById(R.id.text_hts_value);
+ }
+
+ @Override
+ protected void onServiceBinded(final HTSService.RSCBinder binder) {
+ // not used
+ }
+
+ @Override
+ protected void onServiceUnbinded() {
+ // not used
+ }
+
+ @Override
+ protected int getAboutTextId() {
+ return R.string.hts_about_text;
+ }
+
+ @Override
+ protected int getDefaultDeviceName() {
+ return R.string.hts_default_name;
+ }
+
+ @Override
+ protected UUID getFilterUUID() {
+ return HTSManager.HT_SERVICE_UUID;
+ }
+
+ @Override
+ protected Class extends BleProfileService> getServiceClass() {
+ return HTSService.class;
+ }
+
+ @Override
+ public void onServicesDiscovered(boolean optionalServicesFound) {
+ // this may notify user or show some views
+ }
+
+ private void setHTSValueOnView(final double value) {
+ DecimalFormat formattedTemp = new DecimalFormat("#0.00");
+ mHTSValue.setText(formattedTemp.format(value));
+ }
+
+ @Override
+ protected void setDefaultUI() {
+ mHTSValue.setText(R.string.not_available_value);
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final String action = intent.getAction();
+
+ if (HTSService.BROADCAST_HTS_MEASUREMENT.equals(action)) {
+ final double value = intent.getDoubleExtra(HTSService.EXTRA_TEMPERATURE, 0.0f);
+ // Update GUI
+ setHTSValueOnView(value);
+ }
+ }
+ };
+
+ private static IntentFilter makeIntentFilter() {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(HTSService.BROADCAST_HTS_MEASUREMENT);
+ return intentFilter;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManager.java
new file mode 100644
index 000000000..5f7ef22a7
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManager.java
@@ -0,0 +1,306 @@
+/*
+ * 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.hts;
+
+import java.util.List;
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+/**
+ * HTSManager class performs BluetoothGatt operations for connection, service discovery, enabling indication and reading characteristics. All operations required to connect to device with BLE HT
+ * Service and reading health thermometer values are performed here. HTSActivity implements HTSManagerCallbacks in order to receive callbacks of BluetoothGatt operations
+ */
+public class HTSManager implements BleManager {
+ private final String TAG = "HTSManager";
+ private HTSManagerCallbacks mCallbacks;
+ private BluetoothGatt mBluetoothGatt;
+ private Context mContext;
+
+ public final static UUID HT_SERVICE_UUID = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb");
+
+ private static final UUID HT_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-1000-8000-00805f9b34fb");
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+ private final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb");
+ private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb");
+
+ private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
+ private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
+ private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic";
+ private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor";
+ private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information";
+
+ private BluetoothGattCharacteristic mHTCharacteristic, mBatteryCharacteristic;
+
+ private final static int HIDE_MSB_8BITS_OUT_OF_32BITS = 0x00FFFFFF;
+ private final static int HIDE_MSB_8BITS_OUT_OF_16BITS = 0x00FF;
+ private final static int SHIFT_LEFT_8BITS = 8;
+ private final static int SHIFT_LEFT_16BITS = 16;
+ private final static int GET_BIT24 = 0x00400000;
+ private final static int FIRST_BIT_MASK = 0x01;
+
+ private static HTSManager managerInstance = null;
+
+ /**
+ * singleton implementation of HTSManager class
+ */
+ public static synchronized HTSManager getHTSManager() {
+ if (managerInstance == null) {
+ managerInstance = new HTSManager();
+ }
+ return managerInstance;
+ }
+
+ /**
+ * callbacks for activity {HTSActivity} that implements HTSManagerCallbacks interface activity use this method to register itself for receiving callbacks
+ */
+ @Override
+ public void setGattCallbacks(HTSManagerCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void connect(Context context, BluetoothDevice device) {
+ mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
+ mContext = context;
+ }
+
+ @Override
+ public void disconnect() {
+ DebugLogger.d(TAG, "Disconnecting device");
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ }
+ }
+
+ private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+ final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
+
+ DebugLogger.d(TAG, "Bond state changed for: " + device.getAddress() + " new state: " + bondState + " previous: " + previousBondState);
+
+ // skip other devices
+ if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
+ return;
+
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ // We've read Battery Level, now enabling HT indications
+ if (mHTCharacteristic != null) {
+ enableHTIndication();
+ }
+ mContext.unregisterReceiver(this);
+ mCallbacks.onBonded();
+ }
+ }
+ };
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
+ */
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ DebugLogger.d(TAG, "Device connected");
+ mBluetoothGatt.discoverServices();
+ //This will send callback to HTSActivity when device get connected
+ mCallbacks.onDeviceConnected();
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ DebugLogger.d(TAG, "Device disconnected");
+ //This will send callback to HTSActivity when device get disconnected
+ mCallbacks.onDeviceDisconnected();
+ }
+ } else {
+ mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ List services = gatt.getServices();
+ for (BluetoothGattService service : services) {
+ if (service.getUuid().equals(HT_SERVICE_UUID)) {
+ mHTCharacteristic = service.getCharacteristic(HT_MEASUREMENT_CHARACTERISTIC_UUID);
+ } else if (service.getUuid().equals(BATTERY_SERVICE)) {
+ mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC);
+ }
+ }
+ if (mHTCharacteristic != null) {
+ mCallbacks.onServicesDiscovered(false);
+ } else {
+ mCallbacks.onDeviceNotSupported();
+ gatt.disconnect();
+ return;
+ }
+ if (mBatteryCharacteristic != null) {
+ readBatteryLevel();
+ } else {
+ enableHTIndication();
+ }
+ } else {
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC)) {
+ int batteryValue = characteristic.getValue()[0];
+ mCallbacks.onBatteryValueReceived(batteryValue);
+
+ enableHTIndication();
+ }
+ } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+ if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
+ DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
+ mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
+ }
+ } else {
+ mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ if (characteristic.getUuid().equals(HT_MEASUREMENT_CHARACTERISTIC_UUID)) {
+ try {
+ double tempValue = decodeTemperature(characteristic.getValue());
+ mCallbacks.onHTValueReceived(tempValue);
+ } catch (Exception e) {
+ DebugLogger.e(TAG, "invalid temperature value");
+ }
+ }
+ }
+
+ @Override
+ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ // HT indications has been enabled
+ } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+ if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
+ mCallbacks.onBondingRequired();
+
+ final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ mContext.registerReceiver(mBondingBroadcastReceiver, filter);
+ } else {
+ DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
+ mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
+ }
+ } else {
+ DebugLogger.e(TAG, ERROR_WRITE_DESCRIPTOR + " (" + status + ")");
+ mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status);
+ }
+ }
+ };
+
+ public void readBatteryLevel() {
+ if (mBatteryCharacteristic != null) {
+ mBluetoothGatt.readCharacteristic(mBatteryCharacteristic);
+ } else {
+ DebugLogger.e(TAG, "Battery Level Characteristic is null");
+ }
+ }
+
+ /**
+ * enable Health Thermometer indication on Health Thermometer Measurement characteristic
+ */
+ private void enableHTIndication() {
+ mBluetoothGatt.setCharacteristicNotification(mHTCharacteristic, true);
+ BluetoothGattDescriptor descriptor = mHTCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
+ mBluetoothGatt.writeDescriptor(descriptor);
+ }
+
+ /**
+ * This method decode temperature value received from Health Thermometer device First byte {0} of data is flag and first bit of flag shows unit information of temperature. if bit 0 has value 1
+ * then unit is Fahrenheit and Celsius otherwise Four bytes {1 to 4} after Flag bytes represent the temperature value in IEEE-11073 32-bit Float format
+ */
+ private double decodeTemperature(byte[] data) throws Exception {
+ double temperatureValue;
+ byte flag = data[0];
+ byte exponential = data[4];
+ short firstOctet = convertNegativeByteToPositiveShort(data[1]);
+ short secondOctet = convertNegativeByteToPositiveShort(data[2]);
+ short thirdOctet = convertNegativeByteToPositiveShort(data[3]);
+ int mantissa = ((thirdOctet << SHIFT_LEFT_16BITS) | (secondOctet << SHIFT_LEFT_8BITS) | (firstOctet)) & HIDE_MSB_8BITS_OUT_OF_32BITS;
+ mantissa = getTwosComplimentOfNegativeMantissa(mantissa);
+ temperatureValue = (mantissa * Math.pow(10, exponential));
+ /*
+ * Conversion of temperature unit from Fahrenheit to Celsius if unit is in Fahrenheit
+ * Celsius = (98.6*Fahrenheit -32) 5/9
+ */
+ if ((flag & FIRST_BIT_MASK) != 0) {
+ temperatureValue = (float) ((98.6 * temperatureValue - 32) * (5 / 9.0));
+ }
+ return temperatureValue;
+ }
+
+ private short convertNegativeByteToPositiveShort(byte octet) {
+ if (octet < 0) {
+ return (short) (octet & HIDE_MSB_8BITS_OUT_OF_16BITS);
+ } else {
+ return octet;
+ }
+ }
+
+ private int getTwosComplimentOfNegativeMantissa(int mantissa) {
+ if ((mantissa & GET_BIT24) != 0) {
+ return ((((~mantissa) & HIDE_MSB_8BITS_OUT_OF_32BITS) + 1) * (-1));
+ } else {
+ return mantissa;
+ }
+ }
+
+ @Override
+ public void closeBluetoothGatt() {
+ try {
+ mContext.unregisterReceiver(mBondingBroadcastReceiver);
+ } catch (Exception e) {
+ // the receiver must have been not registered or unregistered before
+ }
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ mBatteryCharacteristic = null;
+ mHTCharacteristic = null;
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManagerCallbacks.java
new file mode 100644
index 000000000..9063c9dff
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManagerCallbacks.java
@@ -0,0 +1,39 @@
+/*
+ * 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.hts;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
+
+/**
+ * Interface {@link HTSManagerCallbacks} must be implemented by {@link HTSActivity} in order to receive callbacks from {@link HTSManager}
+ */
+public interface HTSManagerCallbacks extends BleManagerCallbacks {
+
+ /**
+ * Called when Health Thermometer value has been received
+ *
+ * @param value
+ * the new value
+ */
+ public void onHTValueReceived(double value);
+
+}
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
new file mode 100644
index 000000000..d40a0dd89
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java
@@ -0,0 +1,174 @@
+/*
+ * 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.hts;
+
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class HTSService extends BleProfileService implements HTSManagerCallbacks {
+ public static final String BROADCAST_HTS_MEASUREMENT = "no.nordicsemi.android.nrftoolbox.hts.BROADCAST_HTS_MEASUREMENT";
+ public static final String EXTRA_TEMPERATURE = "no.nordicsemi.android.nrftoolbox.hts.EXTRA_TEMPERATURE";
+
+ private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.hts.ACTION_DISCONNECT";
+
+ private final static int NOTIFICATION_ID = 267;
+ private final static int OPEN_ACTIVITY_REQ = 0;
+ private final static int DISCONNECT_REQ = 1;
+
+ private HTSManager mManager;
+ private boolean mBinded;
+
+ private final LocalBinder mBinder = new RSCBinder();
+
+ /**
+ * This local binder is an interface for the binded activity to operate with the HTS sensor
+ */
+ public class RSCBinder extends LocalBinder {
+ // empty
+ }
+
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
+
+ @Override
+ protected BleManager initializeManager() {
+ return mManager = new HTSManager();
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DISCONNECT);
+ registerReceiver(mDisconnectActionBroadcastReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
+ cancelNotification();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
+
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(final Intent intent) {
+ mBinded = true;
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onRebind(final Intent intent) {
+ mBinded = true;
+ // when the activity rebinds to the service, remove the notification
+ cancelNotification();
+
+ // read the battery level when back in the Activity
+ if (isConnected())
+ mManager.readBatteryLevel();
+ }
+
+ @Override
+ public boolean onUnbind(final Intent intent) {
+ mBinded = false;
+ // when the activity closes we need to show the notification that user is connected to the sensor
+ createNotifcation(R.string.hts_notification_connected_message, 0);
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onHTValueReceived(final double value) {
+ final Intent broadcast = new Intent(BROADCAST_HTS_MEASUREMENT);
+ broadcast.putExtra(EXTRA_TEMPERATURE, value);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ /**
+ * Creates the notification
+ *
+ * @param messageResId
+ * message resource id. The message must have one String parameter,
+ * f.e. <string name="name">%s is connected</string>
+ * @param defaults
+ * signals that will be used to notify the user
+ */
+ private void createNotifcation(final int messageResId, final int defaults) {
+ final Intent parentIntent = new Intent(this, FeaturesActivity.class);
+ parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Intent targetIntent = new Intent(this, HTSActivity.class);
+
+ final Intent disconnect = new Intent(ACTION_DISCONNECT);
+ final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // both activities above have launchMode="singleTask" in the AndoridManifest.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);
+ 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);
+
+ final Notification notification = builder.build();
+ final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(NOTIFICATION_ID, notification);
+ }
+
+ /**
+ * Cancels the existing notification. If there is no active notification this method does nothing
+ */
+ private void cancelNotification() {
+ final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(NOTIFICATION_ID);
+ }
+
+ /**
+ * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
+ */
+ private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ Logger.i(getLogSession(), "[HTS] Disconnect action pressed");
+ if (isConnected())
+ getBinder().disconnect();
+ else
+ stopSelf();
+ }
+ };
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManager.java
new file mode 100644
index 000000000..f2810fd9c
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManager.java
@@ -0,0 +1,57 @@
+/*
+ * 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.profile;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+
+public interface BleManager {
+
+ /**
+ * Connects to the Bluetooth Smart device
+ *
+ * @param context
+ * this must be an application context, not the Activity. Call {@link Activity#getApplicationContext()} to get one.
+ * @param device
+ * a device to connect to
+ */
+ public void connect(final Context context, final BluetoothDevice device);
+
+ /**
+ * Disconnects from the device. Does nothing if not connected.
+ */
+ public void disconnect();
+
+ /**
+ * Sets the manager callback listener
+ *
+ * @param callbacks
+ * the callback listener
+ */
+ public void setGattCallbacks(E callbacks);
+
+ /**
+ * Closes and releases resources. May be also used to unregister broadcast listeners.
+ */
+ public void closeBluetoothGatt();
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManagerCallbacks.java
new file mode 100644
index 000000000..18082c660
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManagerCallbacks.java
@@ -0,0 +1,88 @@
+/*
+ * 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.profile;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+
+public interface BleManagerCallbacks {
+
+ /**
+ * Called when the device has been connected. This does not mean that the application may start communication. A service discovery will be handled automatically after this call. Service discovery
+ * may ends up with calling {@link #onServicesDiscovered(boolean)} or {@link #onDeviceNotSupported()} if required services have not been found.
+ */
+ public void onDeviceConnected();
+
+ /**
+ * Called when the device has disconnected (when the callback returned {@link BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} with state DISCONNECTED.
+ */
+ public void onDeviceDisconnected();
+
+ /**
+ * Some profiles may use this method to notify user that the link was lost. You must call this method in youe Ble Manager instead of {@link #onDeviceDisconnected()} while you discover
+ * disconnection not initiated by the user.
+ */
+ public void onLinklossOccur();
+
+ /**
+ * Called when service discovery has finished and primary services has been found. The device is ready to operate. This method is not called if the primary, mandatory services were not found
+ * during service discovery. For example in the Blood Pressure Monitor, a Blood Pressure service is a primary service and Intermediate Cuff Pressure service is a optional secondary service.
+ * Existence of battery service is not notified by this call.
+ *
+ * @param optionalServicesFound
+ * if true the secondary services were also found on the device.
+ */
+ public void onServicesDiscovered(final boolean optionalServicesFound);
+
+ /**
+ * Called when battery value has been received from the device
+ *
+ * @param value
+ * the battery value in percent
+ */
+ public void onBatteryValueReceived(final int value);
+
+ /**
+ * Called when an {@link BluetoothGatt#GATT_INSUFFICIENT_AUTHENTICATION} error occurred and the device bond state is NOT_BONDED
+ */
+ public void onBondingRequired();
+
+ /**
+ * Called when the device has been successfully bonded
+ */
+ public void onBonded();
+
+ /**
+ * Called when a BLE error has occurred
+ *
+ * @param message
+ * the error message
+ * @param errorCode
+ * the error code
+ */
+ public void onError(final String message, final int errorCode);
+
+ /**
+ * Called when service discovery has finished but the main services were not found on the device. This may occur when connecting to bonded device that does not support required services.
+ */
+ public void onDeviceNotSupported();
+}
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
new file mode 100644
index 000000000..855944d02
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileActivity.java
@@ -0,0 +1,396 @@
+/*
+ * 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.profile;
+
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.AppHelpFragment;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public abstract class BleProfileActivity extends ActionBarActivity implements BleManagerCallbacks, ScannerFragment.OnDeviceSelectedListener {
+ private static final String TAG = "BaseProfileActivity";
+
+ private static final String CONNECTION_STATUS = "connection_status";
+ private static final String DEVICE_NAME = "device_name";
+ private static final int REQUEST_ENABLE_BT = 2;
+
+ private BleManager extends BleManagerCallbacks> mBleManager;
+
+ private TextView mDeviceNameView;
+ private TextView mBatteryLevelView;
+ private Button mConnectButton;
+
+ private boolean mDeviceConnected = false;
+ private String mDeviceName;
+
+ @Override
+ protected final void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ensureBLESupported();
+ if (!isBLEEnabled()) {
+ showBLEDialog();
+ }
+
+ /*
+ * We use the managers using a singleton pattern. It's not recommended for the Android, because the singleton instance remains after Activity has been
+ * destroyed but it's simple and is used only for this demo purpose. In final application Managers should be created as a non-static objects in
+ * Services. The Service should implement ManagerCallbacks interface. The application Activity may communicate with such Service using binding,
+ * broadcast listeners, local broadcast listeners (see support.v4 library), or messages. See the Proximity profile for Service approach.
+ */
+ mBleManager = initializeManager();
+ onInitialize(savedInstanceState);
+ onCreateView(savedInstanceState);
+ onViewCreated(savedInstanceState);
+ }
+
+ /**
+ * You may do some initialization here. This method is called from {@link #onCreate(Bundle)} before the view was created.
+ */
+ protected void onInitialize(final Bundle savedInstanceState) {
+ // empty default implementation
+ }
+
+ /**
+ * Called from {@link #onCreate(Bundle)}. This method should build the activity UI, f.e. using {@link #setContentView(int)}. Use to obtain references to
+ * views. Connect/Disconnect button, the device name view and battery level view are manager automatically.
+ *
+ * @param savedInstanceState
+ * contains the data it most recently supplied in {@link #onSaveInstanceState(Bundle)}. Note: Otherwise it is null.
+ */
+ protected abstract void onCreateView(final Bundle savedInstanceState);
+
+ /**
+ * Called after the view has been created.
+ *
+ * @param savedInstanceState
+ */
+ protected final void onViewCreated(final Bundle savedInstanceState) {
+ // set GUI
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ mConnectButton = (Button) findViewById(R.id.action_connect);
+ mDeviceNameView = (TextView) findViewById(R.id.device_name);
+ mBatteryLevelView = (TextView) findViewById(R.id.battery);
+ }
+
+ @Override
+ public void onBackPressed() {
+ mBleManager.disconnect();
+ super.onBackPressed();
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(CONNECTION_STATUS, mDeviceConnected);
+ outState.putString(DEVICE_NAME, mDeviceName);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mDeviceConnected = savedInstanceState.getBoolean(CONNECTION_STATUS);
+ mDeviceName = savedInstanceState.getString(DEVICE_NAME);
+
+ if (mDeviceConnected) {
+ mConnectButton.setText(R.string.action_disconnect);
+ } else {
+ mConnectButton.setText(R.string.action_connect);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.help, menu);
+ return true;
+ }
+
+ /**
+ * Use this method to handle menu actions other than home and about.
+ *
+ * @param itemId
+ * the menu item id
+ * @return true if action has been handled
+ */
+ protected boolean onOptionsItemSelected(final int itemId) {
+ // Overwrite when using menu other than R.menu.help
+ return false;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ final int id = item.getItemId();
+ switch (id) {
+ case android.R.id.home:
+ onBackPressed();
+ break;
+ case R.id.action_about:
+ final AppHelpFragment fragment = AppHelpFragment.getInstance(getAboutTextId());
+ fragment.show(getFragmentManager(), "help_fragment");
+ break;
+ default:
+ return onOptionsItemSelected(id);
+ }
+ return true;
+ }
+
+ /**
+ * Called when user press CONNECT or DISCONNECT button. See layout files -> onClick attribute.
+ */
+ public void onConnectClicked(final View view) {
+ if (isBLEEnabled()) {
+ if (!mDeviceConnected) {
+ setDefaultUI();
+ showDeviceScanningDialog(getFilterUUID(), isDiscoverableRequired());
+ } else {
+ mBleManager.disconnect();
+ }
+ } else {
+ showBLEDialog();
+ }
+ }
+
+ @Override
+ public void onDeviceSelected(final BluetoothDevice device, final String name) {
+ mDeviceNameView.setText(mDeviceName = name);
+ mBleManager.connect(getApplicationContext(), device);
+ }
+
+ @Override
+ public void onDialogCanceled() {
+ // do nothing
+ }
+
+ @Override
+ public void onDeviceConnected() {
+ mDeviceConnected = true;
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mConnectButton.setText(R.string.action_disconnect);
+ }
+ });
+ }
+
+ @Override
+ public void onDeviceDisconnected() {
+ mDeviceConnected = false;
+ mBleManager.closeBluetoothGatt();
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mConnectButton.setText(R.string.action_connect);
+ mDeviceNameView.setText(getDefaultDeviceName());
+ mBatteryLevelView.setText(R.string.not_available);
+ }
+ });
+ }
+
+ @Override
+ public void onLinklossOccur() {
+ mDeviceConnected = false;
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mConnectButton.setText(R.string.action_connect);
+ mDeviceNameView.setText(getDefaultDeviceName());
+ mBatteryLevelView.setText(R.string.not_available);
+ }
+ });
+ }
+
+ @Override
+ public void onBatteryValueReceived(final int value) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBatteryLevelView.setText(getString(R.string.battery, value));
+ }
+ });
+ }
+
+ @Override
+ public void onBondingRequired() {
+ showToast(R.string.bonding);
+ }
+
+ @Override
+ public void onBonded() {
+ showToast(R.string.bonded);
+ }
+
+ @Override
+ public void onError(final String message, final int errorCode) {
+ DebugLogger.e(TAG, "Error occured: " + message + ", error code: " + errorCode);
+ showToast(message + " (" + errorCode + ")");
+
+ // refresh UI when connection failed
+ onDeviceDisconnected();
+ }
+
+ @Override
+ public void onDeviceNotSupported() {
+ showToast(R.string.not_supported);
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param message
+ * a message to be shown
+ */
+ protected void showToast(final String message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(BleProfileActivity.this, message, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param messageResId
+ * an resource id of the message to be shown
+ */
+ protected void showToast(final int messageResId) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(BleProfileActivity.this, messageResId, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * Returns true if the device is connected. Services may not have been discovered yet.
+ */
+ protected boolean isDeviceConnected() {
+ return mDeviceConnected;
+ }
+
+ /**
+ * Returns the name of the device that the phone is currently connected to or was connected last time
+ */
+ protected String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * Initializes the Bluetooth Low Energy manager. A manager is used to communicate with profile's services.
+ *
+ * @return the manager that was created
+ */
+ protected abstract BleManager extends BleManagerCallbacks> initializeManager();
+
+ /**
+ * Restores the default UI before reconnecting
+ */
+ protected abstract void setDefaultUI();
+
+ /**
+ * Returns the default device name resource id. The real device name is obtained when connecting to the device. This one is used when device has
+ * disconnected.
+ *
+ * @return the default device name resource id
+ */
+ protected abstract int getDefaultDeviceName();
+
+ /**
+ * Returns the string resource id that will be shown in About box
+ *
+ * @return the about resource id
+ */
+ protected abstract int getAboutTextId();
+
+ /**
+ * The UUID filter is used to filter out available devices that does not have such UUID in their advertisement packet. See also:
+ * {@link #isChangingConfigurations()}.
+ *
+ * @return the required UUID or null
+ */
+ 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) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final ScannerFragment dialog = ScannerFragment.getInstance(BleProfileActivity.this, filter, discoverableRequired);
+ dialog.show(getFragmentManager(), "scan_fragment");
+ }
+ });
+ }
+
+ private void ensureBLESupported() {
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Toast.makeText(this, R.string.no_ble, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
+ protected boolean isBLEEnabled() {
+ final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ final BluetoothAdapter adapter = bluetoothManager.getAdapter();
+ return adapter != null && adapter.isEnabled();
+ }
+
+ protected void showBLEDialog() {
+ final Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
+ }
+}
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
new file mode 100644
index 000000000..8fe2a34b8
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileExpandableListActivity.java
@@ -0,0 +1,391 @@
+/*
+ * 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.profile;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.AppHelpFragment;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.app.ExpandableListActivity;
+import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+
+public abstract class BleProfileExpandableListActivity extends ExpandableListActivity implements BleManagerCallbacks, ScannerFragment.OnDeviceSelectedListener {
+ private static final String TAG = "BaseProfileActivity";
+
+ private static final String CONNECTION_STATUS = "connection_status";
+ private static final String DEVICE_NAME = "device_name";
+ protected static final int REQUEST_ENABLE_BT = 2;
+
+ private BleManager extends BleManagerCallbacks> mBleManager;
+
+ private TextView mDeviceNameView;
+ private TextView mBatteryLevelView;
+ private Button mConnectButton;
+
+ private boolean mDeviceConnected = false;
+ private String mDeviceName;
+
+ @Override
+ protected final void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ensureBLESupported();
+ if (!isBLEEnabled()) {
+ showBLEDialog();
+ }
+
+ /*
+ * We use the managers using a singleton pattern. It's not recommended for the Android, because the singleton instance remains after Activity has been
+ * destroyed but it's simple and is used only for this demo purpose. In final application Managers should be created as a non-static objects in
+ * Services. The Service should implement ManagerCallbacks interface. The application Activity may communicate with such Service using binding,
+ * broadcast listeners, local broadcast listeners (see support.v4 library), or messages. See the Proximity profile for Service approach.
+ */
+ mBleManager = initializeManager();
+ onInitialize(savedInstanceState);
+ onCreateView(savedInstanceState);
+ onViewCreated(savedInstanceState);
+ }
+
+ /**
+ * You may do some initialization here. This method is called from {@link #onCreate(Bundle)} before the view was created.
+ */
+ protected void onInitialize(final Bundle savedInstanceState) {
+ // empty default implementation
+ }
+
+ /**
+ * Called from {@link #onCreate(Bundle)}. This method should build the activity UI, f.e. using {@link #setContentView(int)}. Use to obtain references to
+ * views. Connect/Disconnect button, the device name view and battery level view are manager automatically.
+ *
+ * @param savedInstanceState contains the data it most recently supplied in {@link #onSaveInstanceState(Bundle)}. Note: Otherwise it is null.
+ */
+ protected abstract void onCreateView(final Bundle savedInstanceState);
+
+ /**
+ * Called after the view has been created.
+ *
+ * @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) {
+ // set GUI
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ mConnectButton = (Button) findViewById(R.id.action_connect);
+ mDeviceNameView = (TextView) findViewById(R.id.device_name);
+ mBatteryLevelView = (TextView) findViewById(R.id.battery);
+ }
+
+ @Override
+ public void onBackPressed() {
+ mBleManager.disconnect();
+ super.onBackPressed();
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(CONNECTION_STATUS, mDeviceConnected);
+ outState.putString(DEVICE_NAME, mDeviceName);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mDeviceConnected = savedInstanceState.getBoolean(CONNECTION_STATUS);
+ mDeviceName = savedInstanceState.getString(DEVICE_NAME);
+
+ if (mDeviceConnected) {
+ mConnectButton.setText(R.string.action_disconnect);
+ } else {
+ mConnectButton.setText(R.string.action_connect);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.help, menu);
+ return true;
+ }
+
+ /**
+ * Use this method to handle menu actions other than home and about.
+ *
+ * @param itemId the menu item id
+ * @return true if action has been handled
+ */
+ protected boolean onOptionsItemSelected(final int itemId) {
+ // Overwrite when using menu other than R.menu.help
+ return false;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ final int id = item.getItemId();
+ switch (id) {
+ case android.R.id.home:
+ onBackPressed();
+ break;
+ case R.id.action_about:
+ final AppHelpFragment fragment = AppHelpFragment.getInstance(getAboutTextId());
+ fragment.show(getFragmentManager(), "help_fragment");
+ break;
+ default:
+ return onOptionsItemSelected(id);
+ }
+ return true;
+ }
+
+ /**
+ * Called when user press CONNECT or DISCONNECT button. See layout files -> onClick attribute.
+ */
+ public void onConnectClicked(final View view) {
+ if (isBLEEnabled()) {
+ if (!mDeviceConnected) {
+ setDefaultUI();
+ showDeviceScanningDialog(getFilterUUID(), isDiscoverableRequired());
+ } else {
+ mBleManager.disconnect();
+ }
+ } else {
+ showBLEDialog();
+ }
+ }
+
+ @Override
+ public void onDeviceSelected(final BluetoothDevice device, final String name) {
+ mDeviceNameView.setText(mDeviceName = name);
+ mBleManager.connect(getApplicationContext(), device);
+ }
+
+ @Override
+ public void onDialogCanceled() {
+ // do nothing
+ }
+
+ @Override
+ public void onDeviceConnected() {
+ mDeviceConnected = true;
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mConnectButton.setText(R.string.action_disconnect);
+ }
+ });
+ }
+
+ @Override
+ public void onDeviceDisconnected() {
+ mDeviceConnected = false;
+ mBleManager.closeBluetoothGatt();
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mConnectButton.setText(R.string.action_connect);
+ mDeviceNameView.setText(getDefaultDeviceName());
+ mBatteryLevelView.setText(R.string.not_available);
+ }
+ });
+ }
+
+ @Override
+ public void onLinklossOccur() {
+ mDeviceConnected = false;
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mConnectButton.setText(R.string.action_connect);
+ mDeviceNameView.setText(getDefaultDeviceName());
+ mBatteryLevelView.setText(R.string.not_available);
+ }
+ });
+ }
+
+ @Override
+ public void onBatteryValueReceived(final int value) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBatteryLevelView.setText(getString(R.string.battery, value));
+ }
+ });
+ }
+
+ @Override
+ public void onBondingRequired() {
+ showToast(R.string.bonding);
+ }
+
+ @Override
+ public void onBonded() {
+ showToast(R.string.bonded);
+ }
+
+ @Override
+ public void onError(final String message, final int errorCode) {
+ DebugLogger.e(TAG, "Error occured: " + message + ", error code: " + errorCode);
+ showToast(message + " (" + errorCode + ")");
+
+ // refresh UI when connection failed
+ onDeviceDisconnected();
+ }
+
+ @Override
+ public void onDeviceNotSupported() {
+ showToast(R.string.not_supported);
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param message a message to be shown
+ */
+ protected void showToast(final String message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(BleProfileExpandableListActivity.this, message, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param messageResId an resource id of the message to be shown
+ */
+ protected void showToast(final int messageResId) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(BleProfileExpandableListActivity.this, messageResId, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * Returns true if the device is connected. Services may not have been discovered yet.
+ */
+ protected boolean isDeviceConnected() {
+ return mDeviceConnected;
+ }
+
+ /**
+ * Returns the name of the device that the phone is currently connected to or was connected last time
+ */
+ protected String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * Initializes the Bluetooth Low Energy manager. A manager is used to communicate with profile's services.
+ *
+ * @return the manager that was created
+ */
+ protected abstract BleManager extends BleManagerCallbacks> initializeManager();
+
+ /**
+ * Restores the default UI before reconnecting
+ */
+ protected abstract void setDefaultUI();
+
+ /**
+ * Returns the default device name resource id. The real device name is obtained when connecting to the device. This one is used when device has
+ * disconnected.
+ *
+ * @return the default device name resource id
+ */
+ protected abstract int getDefaultDeviceName();
+
+ /**
+ * Returns the string resource id that will be shown in About box
+ *
+ * @return the about resource id
+ */
+ protected abstract int getAboutTextId();
+
+ /**
+ * The UUID filter is used to filter out available devices that does not have such UUID in their advertisement packet. See also:
+ * {@link #isChangingConfigurations()}.
+ *
+ * @return the required UUID or null
+ */
+ 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) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final ScannerFragment dialog = ScannerFragment.getInstance(BleProfileExpandableListActivity.this, filter, discoverableRequired);
+ dialog.show(getFragmentManager(), "scan_fragment");
+ }
+ });
+ }
+
+ private void ensureBLESupported() {
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Toast.makeText(this, R.string.no_ble, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
+ protected boolean isBLEEnabled() {
+ final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ final BluetoothAdapter adapter = bluetoothManager.getAdapter();
+ return adapter != null && adapter.isEnabled();
+ }
+
+ protected void showBLEDialog() {
+ final Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java
new file mode 100644
index 000000000..9cfc3bbcc
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java
@@ -0,0 +1,388 @@
+/*
+ * 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.profile;
+
+import no.nordicsemi.android.log.ILogSession;
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.R;
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
+import android.widget.Toast;
+
+public abstract class BleProfileService extends Service implements BleManagerCallbacks {
+ @SuppressWarnings("unused")
+ private static final String TAG = "BleProfileService";
+
+ public static final String BROADCAST_CONNECTION_STATE = "no.nordicsemi.android.nrftoolbox.BROADCAST_CONNECTION_STATE";
+ public static final String BROADCAST_SERVICES_DISCOVERED = "no.nordicsemi.android.nrftoolbox.BROADCAST_SERVICES_DISCOVERED";
+ public static final String BROADCAST_BOND_STATE = "no.nordicsemi.android.nrftoolbox.BROADCAST_BOND_STATE";
+ public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL";
+ public static final String BROADCAST_ERROR = "no.nordicsemi.android.nrftoolbox.BROADCAST_ERROR";
+
+ /** The parameter passed when creating the service. Must contain the address of the sensor that we want to connect to */
+ public static final String EXTRA_DEVICE_ADDRESS = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE_ADDRESS";
+ /** The key for the device name that is returned in {@link #BROADCAST_CONNECTION_STATE} with state {@link #STATE_CONNECTED}. */
+ public static final String EXTRA_DEVICE_NAME = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE_NAME";
+ public static final String EXTRA_LOG_URI = "no.nordicsemi.android.nrftoolbox.EXTRA_LOG_URI";
+ public static final String EXTRA_CONNECTION_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_CONNECTION_STATE";
+ public static final String EXTRA_BOND_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_BOND_STATE";
+ public static final String EXTRA_SERVICE_PRIMARY = "no.nordicsemi.android.nrftoolbox.EXTRA_SERVICE_PRIMARY";
+ public static final String EXTRA_SERVICE_SECONDARY = "no.nordicsemi.android.nrftoolbox.EXTRA_SERVICE_SECONDARY";
+ public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL";
+ public static final String EXTRA_ERROR_MESSAGE = "no.nordicsemi.android.nrftoolbox.EXTRA_ERROR_MESSAGE";
+ public static final String EXTRA_ERROR_CODE = "no.nordicsemi.android.nrftoolbox.EXTRA_ERROR_CODE";
+
+ public static final int STATE_LINK_LOSS = -1;
+ public static final int STATE_DISCONNECTED = 0;
+ public static final int STATE_CONNECTED = 1;
+ public static final int STATE_CONNECTING = 2;
+ public static final int STATE_DISCONNECTING = 3;
+
+ private BleManager mBleManager;
+ private Handler mHandler;
+
+ private boolean mConnected;
+ private String mDeviceAddress;
+ private String mDeviceName;
+ private ILogSession mLogSession;
+
+ public class LocalBinder extends Binder {
+ /**
+ * Disconnects from the sensor.
+ */
+ public final void disconnect() {
+ if (!mConnected) {
+ onDeviceDisconnected();
+ return;
+ }
+
+ // notify user about changing the state to DISCONNECTING
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTING);
+ LocalBroadcastManager.getInstance(BleProfileService.this).sendBroadcast(broadcast);
+
+ mBleManager.disconnect();
+ }
+
+ /**
+ * Returns the device address
+ *
+ * @return device address
+ */
+ public String getDeviceAddress() {
+ return mDeviceAddress;
+ }
+
+ /**
+ * Returns the device name
+ *
+ * @return the device name
+ */
+ public String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * Returns true if the device is connected to the sensor.
+ *
+ * @return true if device is connected to the sensor, false otherwise
+ */
+ public boolean isConnected() {
+ return mConnected;
+ }
+
+ /**
+ * Returns the log session that can be used to append log entries. The log session is created when the service is being created. The method returns null if the nRF Logger app was
+ * not installed.
+ *
+ * @return the log session
+ */
+ protected ILogSession getLogSession() {
+ return mLogSession;
+ }
+ }
+
+ @Override
+ public IBinder onBind(final Intent intent) {
+ return getBinder();
+ }
+
+ /**
+ * Returns the binder implementation. This must return class implementing the additional manager interface that may be used in the binded activity.
+ *
+ * @return the service binder
+ */
+ protected LocalBinder getBinder() {
+ // default implementation returns the basic binder. You can overwrite the LocalBinder with your own, wider implementation
+ return new LocalBinder();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ // we must allow to rebind to the same service
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mHandler = new Handler();
+
+ // initialize the manager
+ mBleManager = initializeManager();
+ mBleManager.setGattCallbacks(this);
+ }
+
+ @SuppressWarnings("rawtypes")
+ protected abstract BleManager initializeManager();
+
+ @Override
+ public int onStartCommand(final Intent intent, final int flags, final int startId) {
+ if (intent == null || !intent.hasExtra(EXTRA_DEVICE_ADDRESS))
+ throw new UnsupportedOperationException("No device address at EXTRA_DEVICE_ADDRESS key");
+
+ final Uri logUri = intent.getParcelableExtra(EXTRA_LOG_URI);
+ mLogSession = Logger.openSession(getApplicationContext(), logUri);
+ mDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
+
+ Logger.i(mLogSession, "Service started");
+
+ // notify user about changing the state to CONNECTING
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTING);
+ LocalBroadcastManager.getInstance(BleProfileService.this).sendBroadcast(broadcast);
+
+ final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
+ final BluetoothAdapter adapter = bluetoothManager.getAdapter();
+ final BluetoothDevice device = adapter.getRemoteDevice(mDeviceAddress);
+ mDeviceName = device.getName();
+ onServiceStarted();
+
+ Logger.v(mLogSession, "Connecting...");
+ mBleManager.connect(BleProfileService.this, device);
+ return START_REDELIVER_INTENT;
+ }
+
+ /**
+ * Called when the service has been started. The device name and address are set. It nRF Logger is installed than logger was also initialized.
+ */
+ protected void onServiceStarted() {
+ // empty default implementation
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ // shutdown the manager
+ mBleManager.closeBluetoothGatt();
+ Logger.i(mLogSession, "Service destroyed");
+ mBleManager = null;
+ mDeviceAddress = null;
+ mDeviceName = null;
+ mConnected = false;
+ mLogSession = null;
+ }
+
+ @Override
+ public void onDeviceConnected() {
+ Logger.i(mLogSession, "Connected to " + mDeviceAddress);
+ mConnected = true;
+
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTED);
+ broadcast.putExtra(EXTRA_DEVICE_ADDRESS, mDeviceAddress);
+ broadcast.putExtra(EXTRA_DEVICE_NAME, mDeviceName);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onDeviceDisconnected() {
+ Logger.i(mLogSession, "Disconnected");
+ mConnected = false;
+ mDeviceAddress = null;
+ mDeviceName = null;
+
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTED);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+
+ // user requested disconnection. We must stop the service
+ Logger.v(mLogSession, "Stopping service...");
+ stopSelf();
+ }
+
+ @Override
+ public void onLinklossOccur() {
+ Logger.w(mLogSession, "Connection lost");
+ mConnected = false;
+
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_LINK_LOSS);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onServicesDiscovered(final boolean optionalServicesFound) {
+ Logger.i(mLogSession, "Services Discovered");
+ Logger.v(mLogSession, "Primary service found");
+ if (optionalServicesFound)
+ Logger.v(mLogSession, "Secondary service found");
+
+ final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED);
+ broadcast.putExtra(EXTRA_SERVICE_PRIMARY, true);
+ broadcast.putExtra(EXTRA_SERVICE_SECONDARY, optionalServicesFound);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onDeviceNotSupported() {
+ Logger.i(mLogSession, "Services Discovered");
+ Logger.w(mLogSession, "Device is not supported");
+
+ final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED);
+ broadcast.putExtra(EXTRA_SERVICE_PRIMARY, false);
+ broadcast.putExtra(EXTRA_SERVICE_SECONDARY, false);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+
+ // no need for disconnecting, it will be disconnected by the manager automatically
+ }
+
+ @Override
+ public void onBatteryValueReceived(final int value) {
+ Logger.i(mLogSession, "Battery level received: " + value + "%");
+
+ final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
+ broadcast.putExtra(EXTRA_BATTERY_LEVEL, value);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onBondingRequired() {
+ Logger.v(mLogSession, "Bond state: Bonding...");
+ showToast(R.string.bonding);
+
+ final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
+ broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onBonded() {
+ Logger.i(mLogSession, "Bond state: Bonded");
+ showToast(R.string.bonded);
+
+ final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
+ broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onError(final String message, final int errorCode) {
+ Logger.e(mLogSession, message + " (" + errorCode + ")");
+
+ final Intent broadcast = new Intent(BROADCAST_ERROR);
+ broadcast.putExtra(EXTRA_ERROR_MESSAGE, message);
+ broadcast.putExtra(EXTRA_ERROR_CODE, errorCode);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+
+ mBleManager.disconnect();
+ stopSelf();
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param messageResId
+ * an resource id of the message to be shown
+ */
+ protected void showToast(final int messageResId) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(BleProfileService.this, messageResId, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param message
+ * a message to be shown
+ */
+ protected void showToast(final String message) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(BleProfileService.this, message, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * Returns the log session that can be used to append log entries. The method returns null if the nRF Logger app was not installed. It is safe to use logger when
+ * {@link #onServiceStarted()} has been called.
+ *
+ * @return the log session
+ */
+ protected ILogSession getLogSession() {
+ return mLogSession;
+ }
+
+ /**
+ * Returns the device address
+ *
+ * @return device address
+ */
+ protected String getDeviceAddress() {
+ return mDeviceAddress;
+ }
+
+ /**
+ * Returns the device name
+ *
+ * @return the device name
+ */
+ protected String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * Returns true if the device is connected to the sensor.
+ *
+ * @return true if device is connected to the sensor, false otherwise
+ */
+ protected boolean isConnected() {
+ return mConnected;
+ }
+}
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
new file mode 100644
index 000000000..6000a21e7
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileServiceReadyActivity.java
@@ -0,0 +1,645 @@
+/*
+ * 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.profile;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.UUID;
+
+import no.nordicsemi.android.log.ILogSession;
+import no.nordicsemi.android.log.LocalLogSession;
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.AppHelpFragment;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+
+/**
+ *
+ * The {@link BleProfileServiceReadyActivity} activity is designed to be the base class for profile activities that uses services in order to connect to the
+ * device. When user press CONNECT button a service is created and the activity binds to it. The service tries to connect to the service and notifies the
+ * activity using Local Broadcasts ({@link LocalBroadcastManager}). See {@link BleProfileService} for messages. If the device is not in range it will listen for
+ * it and connect when it become visible. The service exists until user will press DISCONNECT button.
+ *
+ *
+ * When user closes the activity (f.e. by pressing Back button) while being connected, the Service remains working. It's still connected to the device or still
+ * listens for it. When entering back to the activity, activity will to bind to the service and refresh UI.
+ *
+ */
+public abstract class BleProfileServiceReadyActivity extends ActionBarActivity implements
+ ScannerFragment.OnDeviceSelectedListener {
+ private static final String TAG = "BleProfileServiceReadyActivity";
+
+ private static final String DEVICE_NAME = "device_name";
+ private static final String LOG_URI = "log_uri";
+ protected static final int REQUEST_ENABLE_BT = 2;
+
+ private E mService;
+
+ private TextView mDeviceNameView;
+ private TextView mBatteryLevelView;
+ private Button mConnectButton;
+
+ private ILogSession mLogSession;
+ private String mDeviceName;
+
+ private final BroadcastReceiver mCommonBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final String action = intent.getAction();
+
+ if (BleProfileService.BROADCAST_CONNECTION_STATE.equals(action)) {
+ final int state = intent.getIntExtra(BleProfileService.EXTRA_CONNECTION_STATE, BleProfileService.STATE_DISCONNECTED);
+
+ switch (state) {
+ case BleProfileService.STATE_CONNECTED: {
+ mDeviceName = intent.getStringExtra(BleProfileService.EXTRA_DEVICE_NAME);
+ onDeviceConnected();
+ break;
+ }
+ case BleProfileService.STATE_DISCONNECTED: {
+ onDeviceDisconnected();
+ mDeviceName = null;
+ break;
+ }
+ case BleProfileService.STATE_LINK_LOSS: {
+ onLinklossOccur();
+ break;
+ }
+ case BleProfileService.STATE_CONNECTING:
+ case BleProfileService.STATE_DISCONNECTING:
+ // current implementation does nothing in this states
+ default:
+ // there should be no other actions
+ break;
+
+ }
+ } else if (BleProfileService.BROADCAST_SERVICES_DISCOVERED.equals(action)) {
+ final boolean primaryService = intent.getBooleanExtra(BleProfileService.EXTRA_SERVICE_PRIMARY, false);
+ final boolean secondaryService = intent.getBooleanExtra(BleProfileService.EXTRA_SERVICE_SECONDARY, false);
+
+ if (primaryService) {
+ onServicesDiscovered(secondaryService);
+ } else {
+ onDeviceNotSupported();
+ }
+ } else if (BleProfileService.BROADCAST_BOND_STATE.equals(action)) {
+ final int state = intent.getIntExtra(BleProfileService.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
+ switch (state) {
+ case BluetoothDevice.BOND_BONDING:
+ onBondingRequired();
+ break;
+ case BluetoothDevice.BOND_BONDED:
+ onBonded();
+ break;
+ }
+ } else if (BleProfileService.BROADCAST_BATTERY_LEVEL.equals(action)) {
+ final int value = intent.getIntExtra(BleProfileService.EXTRA_BATTERY_LEVEL, -1);
+ if (value > 0)
+ onBatteryValueReceived(value);
+ } else if (BleProfileService.BROADCAST_ERROR.equals(action)) {
+ final String message = intent.getStringExtra(BleProfileService.EXTRA_ERROR_MESSAGE);
+ final int errorCode = intent.getIntExtra(BleProfileService.EXTRA_ERROR_CODE, 0);
+ onError(message, errorCode);
+ }
+ }
+ };
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public void onServiceConnected(final ComponentName name, final IBinder service) {
+ final E bleService = mService = (E) service;
+ mLogSession = mService.getLogSession();
+ Logger.d(mLogSession, "Activity binded to the service");
+ onServiceBinded(bleService);
+
+ // update UI
+ mDeviceName = bleService.getDeviceName();
+ mDeviceNameView.setText(mDeviceName);
+ mConnectButton.setText(R.string.action_disconnect);
+
+ // and notify user if device is connected
+ if (bleService.isConnected())
+ onDeviceConnected();
+ }
+
+ @Override
+ public void onServiceDisconnected(final ComponentName name) {
+ Logger.d(mLogSession, "Activity disconnected from the service");
+ mDeviceNameView.setText(getDefaultDeviceName());
+ mConnectButton.setText(R.string.action_connect);
+
+ mService = null;
+ mDeviceName = null;
+ mLogSession = null;
+ onServiceUnbinded();
+ }
+ };
+
+ @Override
+ protected final void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ensureBLESupported();
+ if (!isBLEEnabled()) {
+ showBLEDialog();
+ }
+
+ // Restore the old log session
+ if (savedInstanceState != null) {
+ final Uri logUri = savedInstanceState.getParcelable(LOG_URI);
+ mLogSession = Logger.openSession(getApplicationContext(), logUri);
+ }
+
+ /*
+ * In this example we use the ProximityManager in the service. This class communicates with the service using local broadcasts. Final activity may bind
+ * to the Server to use its interface.
+ */
+ onInitialize(savedInstanceState);
+ onCreateView(savedInstanceState);
+ onViewCreated(savedInstanceState);
+
+ LocalBroadcastManager.getInstance(this).registerReceiver(mCommonBroadcastReceiver, makeIntentFilter());
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ /*
+ * If the service has not been started before the following lines will not start it. However, if it's running, the Activity will be binded to it and
+ * notified via mServiceConnection.
+ */
+ final Intent service = new Intent(this, getServiceClass());
+ if (bindService(service, mServiceConnection, 0)) // we pass 0 as a flag so the service will not be created if not exists
+ Logger.d(mLogSession, "Binding to the service..."); // (* - see the comment below)
+
+ /*
+ * * - When user exited the UARTActivity while being connected the log session is kept in the service. We may not get it before binding to it so in this
+ * case this event will not be logged. It will, however, be logged after the orientation changes.
+ */
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ try {
+ Logger.d(mLogSession, "Unbinding from the service...");
+ unbindService(mServiceConnection);
+ mService = null;
+
+ Logger.d(mLogSession, "Activity unbinded from the service");
+ onServiceUnbinded();
+ mDeviceName = null;
+ mLogSession = null;
+ } catch (final IllegalArgumentException e) {
+ // do nothing, we were not connected to the sensor
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mCommonBroadcastReceiver);
+ }
+
+ private static IntentFilter makeIntentFilter() {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(BleProfileService.BROADCAST_CONNECTION_STATE);
+ intentFilter.addAction(BleProfileService.BROADCAST_SERVICES_DISCOVERED);
+ intentFilter.addAction(BleProfileService.BROADCAST_BOND_STATE);
+ intentFilter.addAction(BleProfileService.BROADCAST_BATTERY_LEVEL);
+ intentFilter.addAction(BleProfileService.BROADCAST_ERROR);
+ return intentFilter;
+ }
+
+ /**
+ * Called when activity binds to the service. The parameter is the object returned in {@link Service#onBind(Intent)} method in your service. The method is
+ * called when device gets connected or is created while sensor was connected before. You may use the binder as a sensor interface.
+ */
+ protected abstract void onServiceBinded(E binder);
+
+ /**
+ * Called when activity unbinds from the service. You may no longer use this binder because the sensor was disconnected. This method is also called when you
+ * leave the activity being connected to the sensor in the background.
+ */
+ protected abstract void onServiceUnbinded();
+
+ /**
+ * Returns the service class for sensor communication. The service class must derive from {@link BleProfileService} in order to operate with this class.
+ *
+ * @return the service class
+ */
+ protected abstract Class extends BleProfileService> getServiceClass();
+
+ /**
+ * Returns the service interface that may be used to communicate with the sensor. This will return null if the device is disconnected from the
+ * sensor.
+ *
+ * @return the service binder or null
+ */
+ protected BleProfileService.LocalBinder getService() {
+ return mService;
+ }
+
+ /**
+ * You may do some initialization here. This method is called from {@link #onCreate(Bundle)} before the view was created.
+ */
+ protected void onInitialize(final Bundle savedInstanceState) {
+ // empty default implementation
+ }
+
+ /**
+ * Called from {@link #onCreate(Bundle)}. This method should build the activity UI, f.e. using {@link #setContentView(int)}. Use to obtain references to
+ * views. Connect/Disconnect button, the device name view and battery level view are manager automatically.
+ *
+ * @param savedInstanceState contains the data it most recently supplied in {@link #onSaveInstanceState(Bundle)}. Note: Otherwise it is null.
+ */
+ protected abstract void onCreateView(final Bundle savedInstanceState);
+
+ /**
+ * Called after the view has been created.
+ *
+ * @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) {
+ // set GUI
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ mConnectButton = (Button) findViewById(R.id.action_connect);
+ mDeviceNameView = (TextView) findViewById(R.id.device_name);
+ mBatteryLevelView = (TextView) findViewById(R.id.battery);
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(DEVICE_NAME, mDeviceName);
+ if (mLogSession != null)
+ outState.putParcelable(LOG_URI, mLogSession.getSessionUri());
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mDeviceName = savedInstanceState.getString(DEVICE_NAME);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.help, menu);
+ return true;
+ }
+
+ /**
+ * Use this method to handle menu actions other than home and about.
+ *
+ * @param itemId the menu item id
+ * @return true if action has been handled
+ */
+ protected boolean onOptionsItemSelected(final int itemId) {
+ // Overwrite when using menu other than R.menu.help
+ return false;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ final int id = item.getItemId();
+ switch (id) {
+ case android.R.id.home:
+ onBackPressed();
+ break;
+ case R.id.action_about:
+ final AppHelpFragment fragment = AppHelpFragment.getInstance(getAboutTextId());
+ fragment.show(getFragmentManager(), "help_fragment");
+ break;
+ default:
+ return onOptionsItemSelected(id);
+ }
+ return true;
+ }
+
+ /**
+ * Called when user press CONNECT or DISCONNECT button. See layout files -> onClick attribute.
+ */
+ public void onConnectClicked(final View view) {
+ if (isBLEEnabled()) {
+ if (mService == null) {
+ setDefaultUI();
+ showDeviceScanningDialog(getFilterUUID(), isDiscoverableRequired());
+ } else {
+ Logger.v(mLogSession, "Disconnecting...");
+ mService.disconnect();
+ }
+ } else {
+ showBLEDialog();
+ }
+ }
+
+ /**
+ * Returns the title resource id that will be used to create logger session. If 0 is returned (default) logger will not be used.
+ *
+ * @return the title resource id
+ */
+ protected int getLoggerProfileTitle() {
+ return 0;
+ }
+
+ /**
+ * This method may return the local log content provider authority if local log sessions are supported.
+ *
+ * @return local log session content provider URI
+ */
+ protected Uri getLocalAuthorityLogger() {
+ return null;
+ }
+
+ @Override
+ public void onDeviceSelected(final BluetoothDevice device, final String name) {
+ final int titleId = getLoggerProfileTitle();
+ if (titleId > 0) {
+ mLogSession = Logger.newSession(getApplicationContext(), getString(titleId), device.getAddress(), name);
+ // If nRF Logger is not installed we may want to use local logger
+ if (mLogSession == null && getLocalAuthorityLogger() != null) {
+ mLogSession = LocalLogSession.newSession(getApplicationContext(), getLocalAuthorityLogger(), device.getAddress(), name);
+ }
+ }
+ mDeviceNameView.setText(mDeviceName = name);
+ 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
+ Logger.d(mLogSession, "Creating service...");
+ final Intent service = new Intent(this, getServiceClass());
+ service.putExtra(BleProfileService.EXTRA_DEVICE_ADDRESS, device.getAddress());
+ if (mLogSession != null)
+ service.putExtra(BleProfileService.EXTRA_LOG_URI, mLogSession.getSessionUri());
+ startService(service);
+ Logger.d(mLogSession, "Binding to the service...");
+ bindService(service, mServiceConnection, 0);
+ }
+
+ @Override
+ public void onDialogCanceled() {
+ // do nothing
+ }
+
+ /**
+ * Called when the device has been connected. This does not mean that the application may start communication. A service discovery will be handled
+ * automatically after this call. Service discovery may ends up with calling {@link #onServicesDiscovered(boolean)} or {@link #onDeviceNotSupported()} if required
+ * services have not been found.
+ */
+ public void onDeviceConnected() {
+ mDeviceNameView.setText(mDeviceName);
+ mConnectButton.setText(R.string.action_disconnect);
+ }
+
+ /**
+ * Called when the device has disconnected (when the callback returned
+ * {@link BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} with state DISCONNECTED.
+ */
+ public void onDeviceDisconnected() {
+ mConnectButton.setText(R.string.action_connect);
+ mDeviceNameView.setText(getDefaultDeviceName());
+ if (mBatteryLevelView != null)
+ mBatteryLevelView.setText(R.string.not_available);
+
+ try {
+ Logger.d(mLogSession, "Unbinding from the service...");
+ unbindService(mServiceConnection);
+ mService = null;
+
+ Logger.d(mLogSession, "Activity unbinded from the service");
+ onServiceUnbinded();
+ mDeviceName = null;
+ mLogSession = null;
+ } catch (final IllegalArgumentException e) {
+ // do nothing. This should never happen but does...
+ }
+ }
+
+ /**
+ * Some profiles may use this method to notify user that the link was lost. You must call this method in youe Ble Manager instead of
+ * {@link #onDeviceDisconnected()} while you discover disconnection not initiated by the user.
+ */
+ public void onLinklossOccur() {
+ if (mBatteryLevelView != null)
+ mBatteryLevelView.setText(R.string.not_available);
+ }
+
+ /**
+ * Called when service discovery has finished and primary services has been found. The device is ready to operate. This method is not called if the primary,
+ * mandatory services were not found during service discovery. For example in the Blood Pressure Monitor, a Blood Pressure service is a primary service and
+ * Intermediate Cuff Pressure service is a optional secondary service. Existence of battery service is not notified by this call.
+ *
+ * @param optionalServicesFound if true the secondary services were also found on the device.
+ */
+ public abstract void onServicesDiscovered(final boolean optionalServicesFound);
+
+ /**
+ * Called when the device has started bonding process
+ */
+ public void onBondingRequired() {
+ // empty default implementation
+ }
+
+ /**
+ * Called when the device has finished bonding process successfully
+ */
+ public void onBonded() {
+ // empty default implementation
+ }
+
+ /**
+ * Called when service discovery has finished but the main services were not found on the device. This may occur when connecting to bonded device that does
+ * not support required services.
+ */
+ public void onDeviceNotSupported() {
+ showToast(R.string.not_supported);
+ }
+
+ /**
+ * Called when battery value has been received from the device
+ *
+ * @param value the battery value in percent
+ */
+ public void onBatteryValueReceived(final int value) {
+ if (mBatteryLevelView != null)
+ mBatteryLevelView.setText(getString(R.string.battery, value));
+ }
+
+ /**
+ * Called when a BLE error has occurred
+ *
+ * @param message the error message
+ * @param errorCode the error code
+ */
+ public void onError(final String message, final int errorCode) {
+ DebugLogger.e(TAG, "Error occured: " + message + ", error code: " + errorCode);
+ showToast(message + " (" + errorCode + ")");
+
+ // refresh UI when connection failed
+ onDeviceDisconnected();
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param message a message to be shown
+ */
+ protected void showToast(final String message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(BleProfileServiceReadyActivity.this, message, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param messageResId an resource id of the message to be shown
+ */
+ protected void showToast(final int messageResId) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(BleProfileServiceReadyActivity.this, messageResId, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * Returns true if the device is connected. Services may not have been discovered yet.
+ */
+ protected boolean isDeviceConnected() {
+ return mService != null;
+ }
+
+ /**
+ * Returns the name of the device that the phone is currently connected to or was connected last time
+ */
+ protected String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * Restores the default UI before reconnecting
+ */
+ protected abstract void setDefaultUI();
+
+ /**
+ * Returns the default device name resource id. The real device name is obtained when connecting to the device. This one is used when device has
+ * disconnected.
+ *
+ * @return the default device name resource id
+ */
+ protected abstract int getDefaultDeviceName();
+
+ /**
+ * Returns the string resource id that will be shown in About box
+ *
+ * @return the about resource id
+ */
+ protected abstract int getAboutTextId();
+
+ /**
+ * The UUID filter is used to filter out available devices that does not have such UUID in their advertisement packet. See also:
+ * {@link #isChangingConfigurations()}.
+ *
+ * @return the required UUID or null
+ */
+ 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);
+ dialog.show(getFragmentManager(), "scan_fragment");
+ }
+
+ /**
+ * Returns the log session. Log session is created when the device was selected using the {@link ScannerFragment} and released when user press DISCONNECT.
+ *
+ * @return the logger session or null
+ */
+ public ILogSession getLogSession() {
+ return mLogSession;
+ }
+
+ private void ensureBLESupported() {
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Toast.makeText(this, R.string.no_ble, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
+ protected boolean isBLEEnabled() {
+ final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ final BluetoothAdapter adapter = bluetoothManager.getAdapter();
+ return adapter != null && adapter.isEnabled();
+ }
+
+ protected void showBLEDialog() {
+ final Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/LinklossFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/LinklossFragment.java
new file mode 100644
index 000000000..a374595d5
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/LinklossFragment.java
@@ -0,0 +1,57 @@
+/*
+ * 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.proximity;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.os.Bundle;
+
+public class LinklossFragment extends DialogFragment {
+ private static final String ARG_NAME = "name";
+
+ private String mName;
+
+ public static LinklossFragment getInstance(String name) {
+ final LinklossFragment fragment = new LinklossFragment();
+
+ final Bundle args = new Bundle();
+ args.putString(ARG_NAME, name);
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mName = getArguments().getString(ARG_NAME);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity()).setTitle(getString(R.string.app_name)).setMessage(getString(R.string.proximity_notification_linkloss_alert, mName))
+ .setPositiveButton(R.string.ok, null).create();
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityActivity.java
new file mode 100644
index 000000000..7bb09ceb9
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityActivity.java
@@ -0,0 +1,236 @@
+/*
+ * 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.proximity;
+
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.app.FragmentManager;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+
+public class ProximityActivity extends BleProfileServiceReadyActivity {
+ private static final String TAG = "ProximityActivity";
+
+ public static final String PREFS_GATT_SERVER_ENABLED = "prefs_gatt_server_enabled";
+ private static final String IMMEDIATE_ALERT_STATUS = "immediate_alert_status";
+ private boolean isImmediateAlertOn = false;
+
+ private Button mFindMeButton;
+ private ImageView mLockImage;
+ private CheckBox mGattServerSwitch;
+
+ @Override
+ protected void onCreateView(final Bundle savedInstanceState) {
+ setContentView(R.layout.activity_feature_proximity);
+ setGUI();
+ }
+
+ private void setGUI() {
+ mFindMeButton = (Button) findViewById(R.id.action_findme);
+ mLockImage = (ImageView) findViewById(R.id.imageLock);
+ mGattServerSwitch = (CheckBox) findViewById(R.id.option);
+
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ProximityActivity.this);
+ mGattServerSwitch.setChecked(preferences.getBoolean(PREFS_GATT_SERVER_ENABLED, true));
+ mGattServerSwitch.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
+ preferences.edit().putBoolean(PREFS_GATT_SERVER_ENABLED, isChecked).commit();
+ }
+ });
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(IMMEDIATE_ALERT_STATUS, isImmediateAlertOn);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ isImmediateAlertOn = savedInstanceState.getBoolean(IMMEDIATE_ALERT_STATUS);
+ if (isDeviceConnected()) {
+ showOpenLock();
+
+ if (isImmediateAlertOn) {
+ showSilentMeOnButton();
+ }
+ }
+ }
+
+ @Override
+ protected int getLoggerProfileTitle() {
+ return R.string.proximity_feature_title;
+ }
+
+ @Override
+ protected void onServiceBinded(final ProximityService.ProximityBinder binder) {
+ // you may get the binder instance here
+ }
+
+ @Override
+ protected void onServiceUnbinded() {
+ // you may release the binder instance here
+ }
+
+ @Override
+ protected Class extends BleProfileService> getServiceClass() {
+ return ProximityService.class;
+ }
+
+ @Override
+ protected int getAboutTextId() {
+ return R.string.proximity_about_text;
+ }
+
+ @Override
+ protected int getDefaultDeviceName() {
+ return R.string.proximity_default_name;
+ }
+
+ @Override
+ protected UUID getFilterUUID() {
+ return ProximityManager.LINKLOSS_SERVICE_UUID;
+ }
+
+ /**
+ * Callback of FindMe button on ProximityActivity
+ */
+ public void onFindMeClicked(final View view) {
+ if (isBLEEnabled()) {
+ if (!isDeviceConnected()) {
+ // do nothing
+ } else if (!isImmediateAlertOn) {
+ showSilentMeOnButton();
+ ((ProximityService.ProximityBinder) getService()).startImmediateAlert();
+ isImmediateAlertOn = true;
+ } else {
+ showFindMeOnButton();
+ ((ProximityService.ProximityBinder) getService()).stopImmediateAlert();
+ isImmediateAlertOn = false;
+ }
+ } else {
+ showBLEDialog();
+ }
+ }
+
+ @Override
+ protected void setDefaultUI() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mFindMeButton.setText(R.string.proximity_action_findme);
+ mLockImage.setImageResource(R.drawable.proximity_lock_closed);
+ }
+ });
+ }
+
+ @Override
+ public void onServicesDiscovered(boolean optionalServicesFound) {
+ // this may notify user or update views
+ }
+
+ @Override
+ public void onDeviceConnected() {
+ super.onDeviceConnected();
+ showOpenLock();
+ mGattServerSwitch.setEnabled(false);
+ }
+
+ @Override
+ public void onDeviceDisconnected() {
+ super.onDeviceDisconnected();
+ showClosedLock();
+ mGattServerSwitch.setEnabled(true);
+ }
+
+ @Override
+ public void onBondingRequired() {
+ showClosedLock();
+ }
+
+ @Override
+ public void onBonded() {
+ showOpenLock();
+ }
+
+ @Override
+ public void onLinklossOccur() {
+ super.onLinklossOccur();
+ showClosedLock();
+ resetForLinkloss();
+
+ DebugLogger.w(TAG, "Linkloss occur");
+
+ String deviceName = getDeviceName();
+ if (deviceName == null) {
+ deviceName = getString(R.string.proximity_default_name);
+ }
+
+ showLinklossDialog(deviceName);
+ }
+
+ private void resetForLinkloss() {
+ isImmediateAlertOn = false;
+ setDefaultUI();
+ }
+
+ private void showFindMeOnButton() {
+ mFindMeButton.setText(R.string.proximity_action_findme);
+ }
+
+ private void showSilentMeOnButton() {
+ mFindMeButton.setText(R.string.proximity_action_silentme);
+ }
+
+ private void showOpenLock() {
+ mFindMeButton.setEnabled(true);
+ mLockImage.setImageResource(R.drawable.proximity_lock_open);
+ }
+
+ private void showClosedLock() {
+ mFindMeButton.setEnabled(false);
+ mLockImage.setImageResource(R.drawable.proximity_lock_closed);
+ }
+
+ private void showLinklossDialog(final String name) {
+ try {
+ FragmentManager fm = getFragmentManager();
+ LinklossFragment dialog = LinklossFragment.getInstance(name);
+ dialog.show(fm, "scan_fragment");
+ } catch (final Exception e) {
+ // the activity must have been destroyed
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManager.java
new file mode 100644
index 000000000..381bc1b31
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManager.java
@@ -0,0 +1,469 @@
+/*
+ * 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.proximity;
+
+import java.util.List;
+import java.util.UUID;
+
+import no.nordicsemi.android.log.ILogSession;
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+public class ProximityManager implements BleManager {
+ private final String TAG = "ProximityManager";
+
+ private ProximityManagerCallbacks mCallbacks;
+ private BluetoothGattServer mBluetoothGattServer;
+ private BluetoothGatt mBluetoothGatt;
+ private BluetoothDevice mDeviceToConnect;
+ private Context mContext;
+ private final Handler mHandler;
+ private ILogSession mLogSession;
+ private Ringtone mRingtoneNotification;
+ private Ringtone mRingtoneAlarm;
+
+ public final static UUID IMMEIDIATE_ALERT_SERVICE_UUID = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb");
+ public final static UUID LINKLOSS_SERVICE_UUID = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb");
+
+ private static final UUID ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A06-0000-1000-8000-00805f9b34fb");
+
+ private final static UUID BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb");
+ private final static UUID BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb");
+
+ private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
+ private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
+ private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information";
+ private final static String ERROR_WRITE_CHARACTERISTIC = "Error on writing characteristic";
+ private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic";
+
+ private final static int HIGH_ALERT = 2;
+ private final static int NO_ALERT = 0;
+
+ private BluetoothGattCharacteristic mAlertLevelCharacteristic, mLinklossCharacteristic, mBatteryCharacteristic;
+
+ private boolean userDisconnectedFlag = false;
+
+ public ProximityManager(Context context) {
+ initializeAlarm(context);
+
+ mHandler = new Handler();
+
+ // Register bonding broadcast receiver
+ final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ context.registerReceiver(mBondingBroadcastReceiver, filter);
+ }
+
+ private void openGattServer(Context context, BluetoothManager manager) {
+ mBluetoothGattServer = manager.openGattServer(context, mGattServerCallbacks);
+ }
+
+ private void closeGattServer() {
+ if (mBluetoothGattServer != null) {
+ // mBluetoothGattServer.cancelConnection(mBluetoothGatt.getDevice()); // FIXME this method does not cancel the connection
+ mBluetoothGattServer.close(); // FIXME This method does not cause BluetoothGattServerCallback#onConnectionStateChange(newState=DISCONNECTED) to be called on Nexus phones.
+ mBluetoothGattServer = null;
+ }
+ }
+
+ private void addImmediateAlertService() {
+ /*
+ * This method must be called in UI thread. It works fine on Nexus devices but if called from other thread (f.e. from onServiceAdded in gatt server callback) it hangs the app.
+ */
+ BluetoothGattCharacteristic alertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+ alertLevel.setValue(HIGH_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+ BluetoothGattService immediateAlertService = new BluetoothGattService(IMMEIDIATE_ALERT_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+ immediateAlertService.addCharacteristic(alertLevel);
+ mBluetoothGattServer.addService(immediateAlertService);
+ }
+
+ private void addLinklossService() {
+ /*
+ * This method must be called in UI thread. It works fine on Nexus devices but if called from other thread (f.e. from onServiceAdded in gatt server callback) it hangs the app.
+ */
+ BluetoothGattCharacteristic linklossAlertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE
+ | BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_WRITE);
+ linklossAlertLevel.setValue(HIGH_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+ BluetoothGattService linklossService = new BluetoothGattService(LINKLOSS_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+ linklossService.addCharacteristic(linklossAlertLevel);
+ mBluetoothGattServer.addService(linklossService);
+ }
+
+ private final BluetoothGattServerCallback mGattServerCallbacks = new BluetoothGattServerCallback() {
+ @Override
+ public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+ DebugLogger.d(TAG, "[Proximity Server] onCharacteristicReadRequest " + device.getName());
+ }
+
+ @Override
+ public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset,
+ byte[] value) {
+ DebugLogger.d(TAG, "[Proximity Server] onCharacteristicWriteRequest " + device.getName());
+ final int receivedValue = value[0];
+ if (receivedValue != NO_ALERT) {
+ Logger.i(mLogSession, "[Proximity Server] Immediate alarm request received: ON");
+ playAlarm();
+ } else {
+ Logger.i(mLogSession, "[Proximity Server] Immediate alarm request received: OFF");
+ stopAlarm();
+ }
+ }
+
+ @Override
+ public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+ DebugLogger.d(TAG, "[Proximity Server] onConnectionStateChange " + device.getName() + " status: " + status + " new state: " + newState);
+ }
+
+ @Override
+ public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
+ DebugLogger.d(TAG, "[Proximity Server] onDescriptorReadRequest " + device.getName());
+ }
+
+ @Override
+ public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+ DebugLogger.d(TAG, "[Proximity Server] onDescriptorWriteRequest " + device.getName());
+ }
+
+ @Override
+ public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
+ DebugLogger.d(TAG, "[Proximity Server] onExecuteWrite " + device.getName());
+ }
+
+ @Override
+ public void onServiceAdded(final int status, final BluetoothGattService service) {
+ DebugLogger.d(TAG, "[Proximity Server] onServiceAdded " + service.getUuid());
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // adding another service from callback thread fails on Samsung S4 with Android 4.3
+ if (IMMEIDIATE_ALERT_SERVICE_UUID.equals(service.getUuid()))
+ addLinklossService();
+ else {
+ DebugLogger.d(TAG, "[Proximity Server] Gatt server started");
+ Logger.i(mLogSession, "[Proximity Server] Gatt server started");
+ if (mBluetoothGatt == null) {
+ mBluetoothGatt = mDeviceToConnect.connectGatt(mContext, false, mGattCallback);
+ mDeviceToConnect = null;
+ } else {
+ mBluetoothGatt.connect();
+ }
+ }
+ }
+ });
+ }
+ };
+
+ /**
+ * Callbacks for activity {HTSActivity} that implements HTSManagerCallbacks interface activity use this method to register itself for receiving callbacks
+ */
+ @Override
+ public void setGattCallbacks(ProximityManagerCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ /**
+ * Sets the log session that can be used to log events
+ *
+ * @param logSession
+ */
+ public void setLogger(ILogSession logSession) {
+ mLogSession = logSession;
+ }
+
+ @Override
+ public void connect(Context context, BluetoothDevice device) {
+ mContext = context;
+ // save the device that we want to connect to
+ mDeviceToConnect = device;
+
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ if (preferences.getBoolean(ProximityActivity.PREFS_GATT_SERVER_ENABLED, true)) {
+ final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+ try {
+ DebugLogger.d(TAG, "[Proximity Server] Starting Gatt server...");
+ Logger.v(mLogSession, "[Proximity Server] Starting Gatt server...");
+ openGattServer(context, bluetoothManager);
+ addImmediateAlertService();
+ // the BluetoothGattServerCallback#onServiceAdded callback will proceed further operations
+ } catch (final Exception e) {
+ // On Nexus 4&7 with Android 4.4 (build KRT16S) sometimes creating Gatt Server fails. There is a Null Pointer Exception thrown from addCharacteristic method.
+ Logger.e(mLogSession, "[Proximity Server] Gatt server failed to start");
+ Log.e(TAG, "Creating Gatt Server failed", e);
+ }
+ } else {
+ if (mBluetoothGatt == null) {
+ mBluetoothGatt = mDeviceToConnect.connectGatt(context, false, mGattCallback);
+ mDeviceToConnect = null;
+ } else {
+ mBluetoothGatt.connect();
+ }
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ if (mBluetoothGatt != null) {
+ userDisconnectedFlag = true;
+ mBluetoothGatt.disconnect();
+ stopAlarm();
+ closeGattServer();
+ }
+ }
+
+ private void initializeAlarm(Context context) {
+ final Uri alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
+ mRingtoneAlarm = RingtoneManager.getRingtone(context, alarmUri);
+ mRingtoneAlarm.setStreamType(AudioManager.STREAM_ALARM);
+
+ final Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ mRingtoneNotification = RingtoneManager.getRingtone(context, notification);
+ }
+
+ private void playNotification() {
+ DebugLogger.d(TAG, "playNotification");
+ mRingtoneNotification.play();
+ }
+
+ private void playAlarm() {
+ DebugLogger.d(TAG, "playAlarm");
+ final AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ am.setStreamVolume(AudioManager.STREAM_ALARM, am.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ mRingtoneAlarm.play();
+ }
+
+ private void stopAlarm() {
+ DebugLogger.d(TAG, "stopAlarm");
+ mRingtoneAlarm.stop();
+ }
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
+ */
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ DebugLogger.d(TAG, "Device connected");
+ mBluetoothGatt.discoverServices();
+ //This will send callback to ProximityActivity when device get connected
+ mCallbacks.onDeviceConnected();
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ DebugLogger.d(TAG, "Device disconnected");
+ if (userDisconnectedFlag) {
+ mCallbacks.onDeviceDisconnected();
+ userDisconnectedFlag = false;
+ } else {
+ playNotification();
+ mCallbacks.onLinklossOccur();
+ }
+ }
+ } else {
+ mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ final List services = gatt.getServices();
+ for (BluetoothGattService service : services) {
+ if (service.getUuid().equals(IMMEIDIATE_ALERT_SERVICE_UUID)) {
+ DebugLogger.d(TAG, "Immediate Alert service is found");
+ mAlertLevelCharacteristic = service.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID);
+ } else if (service.getUuid().equals(LINKLOSS_SERVICE_UUID)) {
+ DebugLogger.d(TAG, "Linkloss service is found");
+ mLinklossCharacteristic = service.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID);
+ } else if (service.getUuid().equals(BATTERY_SERVICE_UUID)) {
+ DebugLogger.d(TAG, "Battery service is found");
+ mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID);
+ }
+ }
+ if (mLinklossCharacteristic == null) {
+ mCallbacks.onDeviceNotSupported();
+ gatt.disconnect();
+ } else {
+ mCallbacks.onServicesDiscovered(mAlertLevelCharacteristic != null);
+ writeLinklossAlertLevel(HIGH_ALERT);
+ }
+ } else {
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) {
+ int batteryValue = characteristic.getValue()[0];
+ mCallbacks.onBatteryValueReceived(batteryValue);
+ }
+ } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+ if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
+ DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
+ mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
+ }
+ } else {
+ mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (characteristic.getUuid().equals(ALERT_LEVEL_CHARACTERISTIC_UUID)) {
+ if (mBatteryCharacteristic != null) {
+ readBatteryLevel();
+ }
+ }
+ } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+ if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
+ DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
+ mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
+ }
+ } else {
+ mCallbacks.onError(ERROR_WRITE_CHARACTERISTIC, status);
+ }
+ }
+ };
+
+ private final BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+ final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
+
+ // skip other devices
+ if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
+ return;
+
+ DebugLogger.i(TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState);
+
+ if (bondState == BluetoothDevice.BOND_BONDING) {
+ mCallbacks.onBondingRequired();
+ return;
+ }
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ if (mLinklossCharacteristic != null) {
+ writeLinklossAlertLevel(HIGH_ALERT);
+ }
+ mCallbacks.onBonded();
+ }
+ }
+ };
+
+ private void readBatteryLevel() {
+ if (mBatteryCharacteristic != null) {
+ DebugLogger.d(TAG, "reading battery characteristic");
+ mBluetoothGatt.readCharacteristic(mBatteryCharacteristic);
+ } else {
+ DebugLogger.w(TAG, "Battery Level Characteristic is null");
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void readLinklossAlertLevel() {
+ if (mLinklossCharacteristic != null) {
+ DebugLogger.d(TAG, "reading linkloss alert level characteristic");
+ mBluetoothGatt.readCharacteristic(mLinklossCharacteristic);
+ } else {
+ DebugLogger.w(TAG, "Linkloss Alert Level Characteristic is null");
+ }
+ }
+
+ private void writeLinklossAlertLevel(int alertLevel) {
+ if (mLinklossCharacteristic != null) {
+ DebugLogger.d(TAG, "writing linkloss alert level characteristic");
+ mLinklossCharacteristic.setValue(alertLevel, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+ mBluetoothGatt.writeCharacteristic(mLinklossCharacteristic);
+ } else {
+ DebugLogger.w(TAG, "Linkloss Alert Level Characteristic is not found");
+ }
+ }
+
+ public void writeImmediateAlertOn() {
+ if (mAlertLevelCharacteristic != null) {
+ DebugLogger.d(TAG, "writing Immediate alert characteristic On");
+ mAlertLevelCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
+ mAlertLevelCharacteristic.setValue(HIGH_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+ mBluetoothGatt.writeCharacteristic(mAlertLevelCharacteristic);
+ } else {
+ DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found");
+ }
+ }
+
+ public void writeImmediateAlertOff() {
+ if (mAlertLevelCharacteristic != null) {
+ DebugLogger.d(TAG, "writing Immediate alert characteristic Off");
+ mAlertLevelCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
+ mAlertLevelCharacteristic.setValue(NO_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+ mBluetoothGatt.writeCharacteristic(mAlertLevelCharacteristic);
+ } else {
+ DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found");
+ }
+ }
+
+ @Override
+ public void closeBluetoothGatt() {
+ try {
+ mContext.unregisterReceiver(mBondingBroadcastReceiver);
+ } catch (Exception e) {
+ // the receiver must have been not registered or unregistered before
+ }
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+ if (mBluetoothGattServer != null) {
+ mBluetoothGattServer.close();
+ mBluetoothGattServer = null;
+ }
+ mCallbacks = null;
+ mLogSession = null;
+ mRingtoneAlarm = mRingtoneNotification = null;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManagerCallbacks.java
new file mode 100644
index 000000000..1e0e84e5b
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManagerCallbacks.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.proximity;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
+
+public interface ProximityManagerCallbacks extends BleManagerCallbacks {
+ // no additional methods
+}
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
new file mode 100644
index 000000000..b7eec6b7d
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java
@@ -0,0 +1,184 @@
+/*
+ * 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.proximity;
+
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+
+public class ProximityService extends BleProfileService implements ProximityManagerCallbacks {
+ @SuppressWarnings("unused")
+ private static final String TAG = "ProximityService";
+
+ private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_DISCONNECT";
+
+ private ProximityManager mProximityManager;
+ private boolean mBinded;
+
+ private final static int NOTIFICATION_ID = 100;
+ private final static int OPEN_ACTIVITY_REQ = 0;
+ private final static int DISCONNECT_REQ = 1;
+
+ private final LocalBinder mBinder = new ProximityBinder();
+
+ /**
+ * This local binder is an interface for the binded activity to operate with the proximity sensor
+ */
+ public class ProximityBinder extends LocalBinder {
+ public void startImmediateAlert() {
+ Logger.i(getLogSession(), "[Proximity] Immediate alarm request: ON");
+ mProximityManager.writeImmediateAlertOn();
+ }
+
+ public void stopImmediateAlert() {
+ Logger.i(getLogSession(), "[Proximity] Immediate alarm request: OFF");
+ mProximityManager.writeImmediateAlertOff();
+ }
+ }
+
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
+
+ @Override
+ protected BleManager initializeManager() {
+ return mProximityManager = new ProximityManager(this);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DISCONNECT);
+ registerReceiver(mDisconnectActionBroadcastReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
+ cancelNotification();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
+
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(final Intent intent) {
+ mBinded = true;
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onRebind(final Intent intent) {
+ mBinded = true;
+ // when the activity rebinds to the service, remove the notification
+ cancelNotification();
+ }
+
+ @Override
+ public boolean onUnbind(final Intent intent) {
+ mBinded = false;
+ // when the activity closes we need to show the notification that user is connected to the sensor
+ createNotifcation(R.string.proximity_notification_connected_message, 0);
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ protected void onServiceStarted() {
+ // logger is now available. Assign it to the manager
+ mProximityManager.setLogger(getLogSession());
+ }
+
+ @Override
+ public void onLinklossOccur() {
+ super.onLinklossOccur();
+
+ if (!mBinded) {
+ // when the activity closes we need to show the notification that user is connected to the sensor
+ createNotifcation(R.string.proximity_notification_linkloss_alert, Notification.DEFAULT_ALL);
+ }
+ }
+
+ /**
+ * Creates the notification
+ *
+ * @param messageResId
+ * message resource id. The message must have one String parameter,
+ * f.e. <string name="name">%s is connected</string>
+ * @param defaults
+ * signals that will be used to notify the user
+ */
+ private void createNotifcation(final int messageResId, final int defaults) {
+ final Intent parentIntent = new Intent(this, FeaturesActivity.class);
+ parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Intent targetIntent = new Intent(this, ProximityActivity.class);
+
+ final Intent disconnect = new Intent(ACTION_DISCONNECT);
+ final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // both activities above have launchMode="singleTask" in the AndoridManifest.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);
+ 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);
+
+ final Notification notification = builder.build();
+ final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(NOTIFICATION_ID, notification);
+ }
+
+ /**
+ * Cancels the existing notification. If there is no active notification this method does nothing
+ */
+ private void cancelNotification() {
+ final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(NOTIFICATION_ID);
+ }
+
+ /**
+ * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
+ */
+ private BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ Logger.i(getLogSession(), "[Proximity] Disconnect action pressed");
+ if (isConnected())
+ getBinder().disconnect();
+ else
+ stopSelf();
+ }
+ };
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCActivity.java
new file mode 100644
index 000000000..6d52aab5c
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCActivity.java
@@ -0,0 +1,179 @@
+/*
+ * 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.rsc;
+
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.widget.TextView;
+
+public class RSCActivity extends BleProfileServiceReadyActivity {
+ private TextView mSpeedView;
+ private TextView mCadenceView;
+ private TextView mDistanceView;
+ private TextView mDistanceUnitView;
+ private TextView mTotalDistanceView;
+ private TextView mStridesCountView;
+ private TextView mActivityView;
+
+ @Override
+ protected void onCreateView(final Bundle savedInstanceState) {
+ setContentView(R.layout.activity_feature_rsc);
+ setGui();
+ }
+
+ @Override
+ protected void onInitialize(final Bundle savedInstanceState) {
+ LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, makeIntentFilter());
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);
+ }
+
+ private void setGui() {
+ mSpeedView = (TextView) findViewById(R.id.speed);
+ mCadenceView = (TextView) findViewById(R.id.cadence);
+ mDistanceView = (TextView) findViewById(R.id.distance);
+ mDistanceUnitView = (TextView) findViewById(R.id.distance_unit);
+ mTotalDistanceView = (TextView) findViewById(R.id.total_distance);
+ mStridesCountView = (TextView) findViewById(R.id.strides);
+ mActivityView = (TextView) findViewById(R.id.activity);
+ }
+
+ @Override
+ protected void setDefaultUI() {
+ mSpeedView.setText(R.string.not_available_value);
+ mCadenceView.setText(R.string.not_available_value);
+ mDistanceView.setText(R.string.not_available_value);
+ mDistanceUnitView.setText(R.string.rsc_distance_unit_m);
+ mTotalDistanceView.setText(R.string.not_available_value);
+ mStridesCountView.setText(R.string.not_available_value);
+ mActivityView.setText(R.string.not_available);
+ }
+
+ @Override
+ protected int getLoggerProfileTitle() {
+ return R.string.rsc_feature_title;
+ }
+
+ @Override
+ protected int getDefaultDeviceName() {
+ return R.string.rsc_default_name;
+ }
+
+ @Override
+ protected int getAboutTextId() {
+ return R.string.rsc_about_text;
+ }
+
+ @Override
+ protected Class extends BleProfileService> getServiceClass() {
+ return RSCService.class;
+ }
+
+ @Override
+ protected UUID getFilterUUID() {
+ return RSCManager.RUNNING_SPEED_AND_CADENCE_SERVICE_UUID;
+ }
+
+ @Override
+ protected void onServiceBinded(final RSCService.RSCBinder binder) {
+ // not used
+ }
+
+ @Override
+ protected void onServiceUnbinded() {
+ // not used
+ }
+
+ @Override
+ public void onServicesDiscovered(final boolean optionalServicesFound) {
+ // not used
+ }
+
+ private void onMeasurementReceived(final float speed, final int cadence, final float totalDistance, final int activity) {
+ mSpeedView.setText(String.format("%.1f", speed));
+ mCadenceView.setText(String.format("%d", cadence));
+ if (totalDistance == RSCManagerCallbacks.NOT_AVAILABLE) {
+ mTotalDistanceView.setText(R.string.not_available);
+ } else {
+ mTotalDistanceView.setText(String.format("%.2f", totalDistance / 10000.0f)); // 1km in dm
+ }
+
+ mActivityView.setText(activity == RSCManagerCallbacks.ACTIVITY_RUNNING ? R.string.rsc_running : R.string.rsc_walking);
+ }
+
+ private void onStripsesUpdate(final float distance, final int strides) {
+ if (distance == RSCManagerCallbacks.NOT_AVAILABLE) {
+ mDistanceView.setText(R.string.not_available);
+ mDistanceUnitView.setText(R.string.rsc_distance_unit_m);
+ } else if (distance < 100000) { // 1 km in cm
+ mDistanceView.setText(String.format("%.0f", distance / 100.0f));
+ mDistanceUnitView.setText(R.string.rsc_distance_unit_m);
+ } else {
+ mDistanceView.setText(String.format("%.2f", distance / 100000.0f));
+ mDistanceUnitView.setText(R.string.rsc_distance_unit_km);
+ }
+
+ mStridesCountView.setText(String.valueOf(strides));
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final String action = intent.getAction();
+
+ if (RSCService.BROADCAST_RSC_MEASUREMENT.equals(action)) {
+ final float speed = intent.getFloatExtra(RSCService.EXTRA_SPEED, 0.0f);
+ final int cadence = intent.getIntExtra(RSCService.EXTRA_CADENCE, 0);
+ final float totalDistance = intent.getFloatExtra(RSCService.EXTRA_TOTAL_DISTANCE, RSCManagerCallbacks.NOT_AVAILABLE);
+ final int activity = intent.getIntExtra(RSCService.EXTRA_ACTIVITY, RSCManagerCallbacks.ACTIVITY_WALKING);
+ // Update GUI
+ onMeasurementReceived(speed, cadence, totalDistance, activity);
+ } else if (RSCService.BROADCAST_STRIDES_UPDATE.equals(action)) {
+ final int strides = intent.getIntExtra(RSCService.EXTRA_STRIDES, 0);
+ final float distance = intent.getFloatExtra(RSCService.EXTRA_DISTANCE, 0);
+ // Update GUI
+ onStripsesUpdate(distance, strides);
+ }
+ }
+ };
+
+ private static IntentFilter makeIntentFilter() {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(RSCService.BROADCAST_RSC_MEASUREMENT);
+ intentFilter.addAction(RSCService.BROADCAST_STRIDES_UPDATE);
+ return intentFilter;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManager.java
new file mode 100644
index 000000000..9b27a330e
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManager.java
@@ -0,0 +1,279 @@
+/*
+ * 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.rsc;
+
+import java.util.List;
+import java.util.UUID;
+
+import no.nordicsemi.android.log.ILogSession;
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+public class RSCManager implements BleManager {
+ private static final String TAG = "RSCManager";
+
+ private RSCManagerCallbacks mCallbacks;
+ private BluetoothGatt mBluetoothGatt;
+ private Context mContext;
+ private ILogSession mLogSession;
+
+ private static final byte INSTANTANEOUS_STRIDE_LENGTH_PRESENT = 0x01; // 1 bit
+ private static final byte TOTAL_DISTANCE_PRESENT = 0x02; // 1 bit
+ private static final byte WALKING_OR_RUNNING_STATUS_BITS = 0x04; // 1 bit
+
+ public final static UUID RUNNING_SPEED_AND_CADENCE_SERVICE_UUID = UUID.fromString("00001814-0000-1000-8000-00805f9b34fb");
+ /** Running Speed and Cadence Measurement characteristic */
+ private static final UUID RSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A53-0000-1000-8000-00805f9b34fb");
+
+ private final static UUID BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb");
+ /** Battery Level characteristic */
+ private final static UUID BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb");
+ /** Client configuration descriptor that will allow us to enable notifications and indications */
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+ private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
+ private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
+ private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information";
+ private final static String ERROR_WRITE_CHARACTERISTIC = "Error on writing characteristic";
+ private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic";
+
+ private BluetoothGattCharacteristic mRSCMeasurementCharacteristic, mBatteryCharacteristic;
+
+ public RSCManager(final Context context) {
+ // Register bonding broadcast receiver
+ final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ context.registerReceiver(mBondingBroadcastReceiver, filter);
+ }
+
+ @Override
+ public void setGattCallbacks(final RSCManagerCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ public void setLogger(final ILogSession session) {
+ mLogSession = session;
+ }
+
+ @Override
+ public void connect(final Context context, final BluetoothDevice device) {
+ mContext = context;
+
+ Logger.i(mLogSession, "[RSC] Gatt server started");
+ if (mBluetoothGatt == null) {
+ mBluetoothGatt = device.connectGatt(mContext, true, mGattCallback);
+ } else {
+ mBluetoothGatt.connect();
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ }
+ }
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
+ */
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ DebugLogger.d(TAG, "Device connected");
+ mBluetoothGatt.discoverServices();
+ //This will send callback to RSCActivity when device get connected
+ mCallbacks.onDeviceConnected();
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ DebugLogger.d(TAG, "Device disconnected");
+
+ // TODO It should check whether the user has requested disconnection or was it link loss. On Samsung S4 the DevKit reconnects itself just after linkloss but the Service is already dead.
+ mCallbacks.onDeviceDisconnected();
+ closeBluetoothGatt();
+ }
+ } else {
+ mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ final List services = gatt.getServices();
+ for (BluetoothGattService service : services) {
+ if (service.getUuid().equals(RUNNING_SPEED_AND_CADENCE_SERVICE_UUID)) {
+ DebugLogger.d(TAG, "Running Speed and Cadence service is found");
+ mRSCMeasurementCharacteristic = service.getCharacteristic(RSC_MEASUREMENT_CHARACTERISTIC_UUID);
+ } else if (service.getUuid().equals(BATTERY_SERVICE_UUID)) {
+ DebugLogger.d(TAG, "Battery service is found");
+ mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID);
+ }
+ }
+ if (mRSCMeasurementCharacteristic == null) {
+ mCallbacks.onDeviceNotSupported();
+ gatt.disconnect();
+ } else {
+ mCallbacks.onServicesDiscovered(false /* more characteristics not supported */);
+
+ // We have discovered services, let's start notifications and indications, one by one: battery, icp (if exists), bpm
+ if (mBatteryCharacteristic != null) {
+ readBatteryLevel(gatt);
+ } else {
+ enableRSCMeasurementNotification(gatt);
+ }
+ }
+ } else {
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) {
+ int batteryValue = characteristic.getValue()[0];
+ mCallbacks.onBatteryValueReceived(batteryValue);
+
+ enableRSCMeasurementNotification(gatt);
+ }
+ } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+ if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
+ DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
+ mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
+ }
+ } else {
+ mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
+ // Decode the new data
+ int offset = 0;
+ final int flags = characteristic.getValue()[offset]; // 1 byte
+ offset += 1;
+
+ final boolean islmPresent = (flags & INSTANTANEOUS_STRIDE_LENGTH_PRESENT) > 0;
+ final boolean tdPreset = (flags & TOTAL_DISTANCE_PRESENT) > 0;
+ final boolean running = (flags & WALKING_OR_RUNNING_STATUS_BITS) > 0;
+
+ final float instantaneousSpeed = (float) characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset) / 256.0f * 3.6f; // 1/256 m/s in km/h
+ offset += 2;
+
+ final int instantaneousCadence = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
+ offset += 1;
+
+ float instantaneousStrideLength = RSCManagerCallbacks.NOT_AVAILABLE;
+ if (islmPresent) {
+ instantaneousStrideLength = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
+ offset += 2;
+ }
+
+ float totalDistance = RSCManagerCallbacks.NOT_AVAILABLE;
+ if (tdPreset) {
+ totalDistance = (float) characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset) / 10.0f;
+ offset += 4;
+ }
+
+ // Notify listener about the new measurement
+ mCallbacks.onMeasurementReceived(instantaneousSpeed, instantaneousCadence, totalDistance, instantaneousStrideLength, running ? RSCManagerCallbacks.ACTIVITY_RUNNING
+ : RSCManagerCallbacks.ACTIVITY_WALKING);
+ }
+ };
+
+ private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+ final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
+
+ // skip other devices
+ if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
+ return;
+
+ DebugLogger.i(TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState);
+
+ if (bondState == BluetoothDevice.BOND_BONDING) {
+ mCallbacks.onBondingRequired();
+ return;
+ }
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ mCallbacks.onBonded();
+ }
+ }
+ };
+
+ public void readBatteryLevel() {
+ readBatteryLevel(mBluetoothGatt);
+ }
+
+ private void readBatteryLevel(final BluetoothGatt gatt) {
+ if (mBatteryCharacteristic != null) {
+ DebugLogger.d(TAG, "reading battery characteristic");
+ gatt.readCharacteristic(mBatteryCharacteristic);
+ } else {
+ DebugLogger.w(TAG, "Battery Level Characteristic is null");
+ }
+ }
+
+ /**
+ * Enabling notification on RSC Measurement Characteristic
+ */
+ private void enableRSCMeasurementNotification(final BluetoothGatt gatt) {
+ DebugLogger.d(TAG, "enableIntermediateCuffPressureNotification()");
+ gatt.setCharacteristicNotification(mRSCMeasurementCharacteristic, true);
+ final BluetoothGattDescriptor descriptor = mRSCMeasurementCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ }
+
+ @Override
+ public void closeBluetoothGatt() {
+ try {
+ mContext.unregisterReceiver(mBondingBroadcastReceiver);
+ } catch (Exception e) {
+ // the receiver must have been not registered or unregistered before
+ }
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+ mCallbacks = null;
+ mLogSession = null;
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManagerCallbacks.java
new file mode 100644
index 000000000..6fe5d8bf0
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManagerCallbacks.java
@@ -0,0 +1,32 @@
+/*
+ * 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.rsc;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
+
+public interface RSCManagerCallbacks extends BleManagerCallbacks {
+ public static final int NOT_AVAILABLE = -1;
+ public static final int ACTIVITY_WALKING = 0;
+ public static final int ACTIVITY_RUNNING = 1;
+
+ public void onMeasurementReceived(float speed, int cadence, float distance, float strideLen, int activity);
+}
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
new file mode 100644
index 000000000..0153928cd
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java
@@ -0,0 +1,236 @@
+/*
+ * 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.rsc;
+
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class RSCService extends BleProfileService implements RSCManagerCallbacks {
+ private static final String TAG = "RSCService";
+
+ public static final String BROADCAST_RSC_MEASUREMENT = "no.nordicsemi.android.nrftoolbox.rsc.BROADCAST_RSC_MEASUREMENT";
+ public static final String EXTRA_SPEED = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_SPEED";
+ public static final String EXTRA_CADENCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_CADENCE";
+ public static final String EXTRA_TOTAL_DISTANCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_TOTAL_DISTANCE";
+ public static final String EXTRA_ACTIVITY = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_ACTIVITY";
+
+ public static final String BROADCAST_STRIDES_UPDATE = "no.nordicsemi.android.nrftoolbox.rsc.BROADCAST_STRIDES_UPDATE";
+ public static final String EXTRA_STRIDES = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_STRIDES";
+ public static final String EXTRA_DISTANCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_DISTANCE";
+
+ private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.rsc.ACTION_DISCONNECT";
+
+ private RSCManager mManager;
+ private boolean mBinded;
+
+ /** The last value of a cadence */
+ private float mCadence;
+ /** Trip distance in cm */
+ private float mDistance;
+ /** Stride length in cm */
+ private float mStrideLength;
+ /** Number of steps in the trip */
+ private int mStepsNumber;
+ private boolean mTaskInProgress;
+ private final Handler mHandler = new Handler();
+
+ private final static int NOTIFICATION_ID = 200;
+ private final static int OPEN_ACTIVITY_REQ = 0;
+ private final static int DISCONNECT_REQ = 1;
+
+ private final LocalBinder mBinder = new RSCBinder();
+
+ /**
+ * This local binder is an interface for the binded activity to operate with the RSC sensor
+ */
+ public class RSCBinder extends LocalBinder {
+ // empty
+ }
+
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
+
+ @Override
+ protected BleManager initializeManager() {
+ return mManager = new RSCManager(this);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DISCONNECT);
+ registerReceiver(mDisconnectActionBroadcastReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
+ cancelNotification();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
+
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(final Intent intent) {
+ mBinded = true;
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onRebind(final Intent intent) {
+ mBinded = true;
+ // when the activity rebinds to the service, remove the notification
+ cancelNotification();
+
+ // read the battery level when back in the Activity
+ if (isConnected())
+ mManager.readBatteryLevel();
+ }
+
+ @Override
+ public boolean onUnbind(final Intent intent) {
+ mBinded = false;
+ // when the activity closes we need to show the notification that user is connected to the sensor
+ createNotifcation(R.string.rsc_notification_connected_message, 0);
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ protected void onServiceStarted() {
+ // logger is now available. Assign it to the manager
+ mManager.setLogger(getLogSession());
+ }
+
+ private final Runnable mUpdateStridesTask = new Runnable() {
+ @Override
+ public void run() {
+ if (!isConnected())
+ return;
+
+ mStepsNumber++;
+ mDistance += mStrideLength;
+ final Intent broadcast = new Intent(BROADCAST_STRIDES_UPDATE);
+ broadcast.putExtra(EXTRA_STRIDES, mStepsNumber);
+ broadcast.putExtra(EXTRA_DISTANCE, mDistance);
+ LocalBroadcastManager.getInstance(RSCService.this).sendBroadcast(broadcast);
+
+ if (mCadence > 0) {
+ final long interval = (long) (1000.0f * 65.0f / mCadence); // 60s + 5s for calibration in milliseconds
+ mHandler.postDelayed(mUpdateStridesTask, interval);
+ } else {
+ mTaskInProgress = false;
+ }
+ }
+ };
+
+ @Override
+ public void onMeasurementReceived(final float speed, final int cadence, final float totalDistance, final float strideLen, final int activity) {
+ final Intent broadcast = new Intent(BROADCAST_RSC_MEASUREMENT);
+ broadcast.putExtra(EXTRA_SPEED, speed);
+ broadcast.putExtra(EXTRA_CADENCE, cadence);
+ broadcast.putExtra(EXTRA_TOTAL_DISTANCE, totalDistance);
+ broadcast.putExtra(EXTRA_ACTIVITY, activity);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+
+ // Start strides counter if not in progress
+ mCadence = cadence;
+ mStrideLength = strideLen;
+ if (!mTaskInProgress && cadence > 0) {
+ mTaskInProgress = true;
+
+ final long interval = (long) (1000.0f * 65.0f / mCadence); // 60s + 5s for calibration in milliseconds
+ mHandler.postDelayed(mUpdateStridesTask, interval);
+ }
+ }
+
+ /**
+ * Creates the notification
+ *
+ * @param messageResIdthe
+ * message resource id. The message must have one String parameter,
+ * f.e. <string name="name">%s is connected</string>
+ * @param defaults
+ * signals that will be used to notify the user
+ */
+ private void createNotifcation(final int messageResId, final int defaults) {
+ final Intent parentIntent = new Intent(this, FeaturesActivity.class);
+ parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Intent targetIntent = new Intent(this, RSCActivity.class);
+
+ final Intent disconnect = new Intent(ACTION_DISCONNECT);
+ final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // both activities above have launchMode="singleTask" in the AndoridManifest.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);
+ 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);
+
+ final Notification notification = builder.build();
+ final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(NOTIFICATION_ID, notification);
+ }
+
+ /**
+ * Cancels the existing notification. If there is no active notification this method does nothing
+ */
+ private void cancelNotification() {
+ final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(NOTIFICATION_ID);
+ }
+
+ /**
+ * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
+ */
+ private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ Logger.i(getLogSession(), "[RSC] Disconnect action pressed");
+ if (isConnected())
+ getBinder().disconnect();
+ else
+ stopSelf();
+ }
+ };
+
+}
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
new file mode 100644
index 000000000..d4dfd3814
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/DeviceListAdapter.java
@@ -0,0 +1,217 @@
+/*
+ * 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 java.util.ArrayList;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * DeviceListAdapter class is list adapter for showing scanned Devices name, address and RSSI image based on RSSI values.
+ */
+public class DeviceListAdapter extends BaseAdapter {
+ private static final int TYPE_TITLE = 0;
+ private static final int TYPE_ITEM = 1;
+ private static final int TYPE_EMPTY = 2;
+
+ 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
+ */
+ 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();
+ }
+ }
+
+ /**
+ * 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
+ */
+ 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;
+ }
+ mListValues.add(device);
+ notifyDataSetChanged();
+ }
+
+ public void clearDevices() {
+ mListValues.clear();
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ final int bondedCount = mListBondedValues.size() + 1; // 1 for the title
+ final int availableCount = mListValues.isEmpty() ? 2 : mListValues.size() + 1; // 1 for title, 1 for empty text
+ if (bondedCount == 1)
+ return availableCount;
+ return bondedCount + availableCount;
+ }
+
+ @Override
+ 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;
+ else
+ return mListValues.get(position - 1);
+ } else {
+ if (position == 0)
+ return R.string.scanner_subtitle_bonded;
+ if (position < bondedCount)
+ return mListBondedValues.get(position - 1);
+ if (position == bondedCount)
+ return R.string.scanner_subtitle__not_bonded;
+ return mListValues.get(position - bondedCount - 1);
+ }
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 3;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItemViewType(position) == TYPE_ITEM;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (position == 0)
+ return TYPE_TITLE;
+
+ if (!mListBondedValues.isEmpty() && position == mListBondedValues.size() + 1)
+ return TYPE_TITLE;
+
+ if (position == getCount() - 1 && mListValues.isEmpty())
+ return TYPE_EMPTY;
+
+ return TYPE_ITEM;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View oldView, ViewGroup parent) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final int type = getItemViewType(position);
+
+ View view = oldView;
+ switch (type) {
+ case TYPE_EMPTY:
+ if (view == null) {
+ view = inflater.inflate(R.layout.device_list_empty, parent, false);
+ }
+ break;
+ case TYPE_TITLE:
+ if (view == null) {
+ view = inflater.inflate(R.layout.device_list_title, parent, false);
+ }
+ final TextView title = (TextView) view;
+ title.setText((Integer) getItem(position));
+ break;
+ default:
+ if (view == null) {
+ view = inflater.inflate(R.layout.device_list_row, parent, false);
+ final ViewHolder holder = new ViewHolder();
+ holder.name = (TextView) view.findViewById(R.id.name);
+ holder.address = (TextView) view.findViewById(R.id.address);
+ holder.rssi = (ImageView) view.findViewById(R.id.rssi);
+ view.setTag(holder);
+ }
+
+ final ExtendedBluetoothDevice device = (ExtendedBluetoothDevice) getItem(position);
+ final ViewHolder holder = (ViewHolder) view.getTag();
+ 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) {
+ final int rssiPercent = (int) (100.0f * (127.0f + device.rssi) / (127.0f + 20.0f));
+ holder.rssi.setImageLevel(rssiPercent);
+ holder.rssi.setVisibility(View.VISIBLE);
+ } else {
+ holder.rssi.setVisibility(View.GONE);
+ }
+ break;
+ }
+
+ return view;
+ }
+
+ private class ViewHolder {
+ private TextView name;
+ private TextView address;
+ private ImageView rssi;
+ }
+}
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
new file mode 100644
index 000000000..35899ab44
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ExtendedBluetoothDevice.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+public class ExtendedBluetoothDevice {
+ 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;
+ }
+
+ @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);
+ }
+
+ /**
+ * 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);
+ }
+ }
+}
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
new file mode 100644
index 000000000..2c0c57add
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerFragment.java
@@ -0,0 +1,281 @@
+/*
+ * 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 java.util.Set;
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.ListView;
+
+/**
+ * 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
+ */
+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 BluetoothAdapter mBluetoothAdapter;
+ private OnDeviceSelectedListener mListener;
+ private DeviceListAdapter mAdapter;
+ private final Handler mHandler = new Handler();
+ private Button mScanButton;
+
+ private boolean mDiscoverableRequired;
+ private UUID mUuid;
+
+ private boolean mIsScanning = false;
+
+ private static final boolean DEVICE_IS_BONDED = true;
+ private static final boolean DEVICE_NOT_BONDED = false;
+ /* package */static final int NO_RSSI = -1000;
+
+ /**
+ * 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) {
+ final ScannerFragment fragment = new ScannerFragment();
+
+ final Bundle args = new Bundle();
+ args.putParcelable(PARAM_UUID, new ParcelUuid(uuid));
+ args.putBoolean(DISCOVERABLE_REQUIRED, discoverableRequired);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ /**
+ * Interface required to be implemented by activity.
+ */
+ public static interface OnDeviceSelectedListener {
+ /**
+ * Fired when user selected the device.
+ *
+ * @param device
+ * the device to connect to
+ * @param name
+ * the device name. Unfortunately on some devices {@link BluetoothDevice#getName()} always returns null, f.e. Sony Xperia Z1 (C6903) with Android 4.3. The name has to
+ * be parsed manually form the Advertisement packet.
+ */
+ public void onDeviceSelected(final BluetoothDevice device, final String name);
+
+ /**
+ * Fired when scanner dialog has been cancelled without selecting a device.
+ */
+ public void onDialogCanceled();
+ }
+
+ /**
+ * This will make sure that {@link OnDeviceSelectedListener} interface is implemented by activity.
+ */
+ @Override
+ public void onAttach(final Activity activity) {
+ super.onAttach(activity);
+ try {
+ this.mListener = (OnDeviceSelectedListener) activity;
+ } catch (final ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement OnDeviceSelectedListener");
+ }
+ }
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Bundle args = getArguments();
+ if (args.containsKey(PARAM_UUID)) {
+ final ParcelUuid pu = args.getParcelable(PARAM_UUID);
+ mUuid = pu.getUuid();
+ }
+ mDiscoverableRequired = args.getBoolean(DISCOVERABLE_REQUIRED);
+
+ final BluetoothManager manager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
+ mBluetoothAdapter = manager.getAdapter();
+ }
+
+ @Override
+ public void onDestroyView() {
+ stopScan();
+ super.onDestroyView();
+ }
+
+ /**
+ * When dialog is created then set AlertDialog with list and button views.
+ */
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ final View dialogView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_device_selection, null);
+ final ListView listview = (ListView) dialogView.findViewById(android.R.id.list);
+
+ listview.setEmptyView(dialogView.findViewById(android.R.id.empty));
+ listview.setAdapter(mAdapter = new DeviceListAdapter(getActivity()));
+
+ builder.setTitle(R.string.scanner_title);
+ final AlertDialog dialog = builder.setView(dialogView).create();
+ listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(final AdapterView> parent, final View view, final int position, final long id) {
+ stopScan();
+ dialog.dismiss();
+ final ExtendedBluetoothDevice d = (ExtendedBluetoothDevice) mAdapter.getItem(position);
+ mListener.onDeviceSelected(d.device, d.name);
+ }
+ });
+
+ mScanButton = (Button) dialogView.findViewById(R.id.action_cancel);
+ mScanButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.action_cancel) {
+ if (mIsScanning) {
+ dialog.cancel();
+ } else {
+ startScan();
+ }
+ }
+ }
+ });
+
+ addBondedDevices();
+ if (savedInstanceState == null)
+ startScan();
+ return dialog;
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+
+ mListener.onDialogCanceled();
+ }
+
+ /**
+ * 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() {
+ 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);
+
+ mIsScanning = true;
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (mIsScanning) {
+ stopScan();
+ }
+ }
+ }, SCAN_DURATION);
+ }
+
+ /**
+ * Stop scan if user tap Cancel button.
+ */
+ private void stopScan() {
+ if (mIsScanning) {
+ mScanButton.setText(R.string.scanner_action_scan);
+ mBluetoothAdapter.stopLeScan(mLEScanCallback);
+ 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));
+ }
+ }
+
+ /**
+ * 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);
+ }
+ });
+ }
+
+ /**
+ * 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());
+ }
+ }
+ }
+ };
+}
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
new file mode 100644
index 000000000..046e1e299
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerServiceParser.java
@@ -0,0 +1,169 @@
+/*
+ * 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 java.io.UnsupportedEncodingException;
+import java.util.UUID;
+
+import android.bluetooth.BluetoothDevice;
+import android.util.Log;
+
+/**
+ * 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 connectable (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/uart/UARTActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java
new file mode 100644
index 000000000..02fbac6d9
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java
@@ -0,0 +1,240 @@
+/*
+ * 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 java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothDevice;
+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.support.v4.widget.SlidingPaneLayout;
+import android.view.View;
+
+public class UARTActivity extends BleProfileServiceReadyActivity implements UARTControlFragment.ControlFragmentListener, UARTInterface {
+ private final static String SIS_EDIT_MODE = "sis_edit_mode";
+
+ private SlidingPaneLayout mSlider;
+ private UARTService.UARTBinder mServiceBinder;
+ private boolean mEditMode;
+
+ @Override
+ protected Class extends BleProfileService> getServiceClass() {
+ return UARTService.class;
+ }
+
+ @Override
+ protected void onServiceBinded(final UARTService.UARTBinder binder) {
+ mServiceBinder = binder;
+ }
+
+ @Override
+ protected void onServiceUnbinded() {
+ mServiceBinder = null;
+ }
+
+ @Override
+ protected void onCreateView(final Bundle savedInstanceState) {
+ setContentView(R.layout.activity_feature_uart);
+
+ // Setup the sliding pane if it exists
+ final SlidingPaneLayout slidingPane = mSlider = (SlidingPaneLayout) findViewById(R.id.sliding_pane);
+ if (slidingPane != null) {
+ slidingPane.setSliderFadeColor(Color.TRANSPARENT);
+ slidingPane.setShadowResourceLeft(R.drawable.shadow_r);
+ slidingPane.setPanelSlideListener(new SlidingPaneLayout.SimplePanelSlideListener() {
+ @Override
+ public void onPanelClosed(final View panel) {
+ // Close the keyboard
+ final UARTLogFragment logFragment = (UARTLogFragment) getFragmentManager().findFragmentById(R.id.fragment_log);
+ logFragment.onFragmentHidden();
+ }
+ });
+ }
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ mEditMode = savedInstanceState.getBoolean(SIS_EDIT_MODE);
+ setEditMode(mEditMode, false);
+ }
+
+ @Override
+ public void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(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
+ }
+
+ @Override
+ public void onDeviceSelected(final BluetoothDevice device, final String name) {
+ // The super method starts the service
+ super.onDeviceSelected(device, name);
+
+ // Notify the log fragment about it
+ final UARTLogFragment logFragment = (UARTLogFragment) getFragmentManager().findFragmentById(R.id.fragment_log);
+ logFragment.onServiceStarted();
+ }
+
+ @Override
+ protected int getDefaultDeviceName() {
+ return R.string.uart_default_name;
+ }
+
+ @Override
+ protected int getAboutTextId() {
+ return R.string.uart_about_text;
+ }
+
+ @Override
+ 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);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mSlider != null && mSlider.isOpen()) {
+ mSlider.closePane();
+ return;
+ }
+ if (mEditMode) {
+ final UARTControlFragment fragment = (UARTControlFragment) getFragmentManager().findFragmentById(R.id.fragment_control);
+ fragment.setEditMode(false);
+ return;
+ }
+ super.onBackPressed();
+ }
+
+ /**
+ * Updates the ActionBar background color depending on whether we are in edit mode or not.
+ *
+ * @param editMode
+ * true to show edit mode, false otherwise
+ * @param change
+ * if true the background will change with animation, otherwise immediately
+ */
+ @SuppressLint("NewApi")
+ private void setEditMode(final boolean editMode, final boolean change) {
+ mEditMode = editMode;
+ if (!change) {
+ final ColorDrawable color = new ColorDrawable();
+ int darkColor = 0;
+ if (editMode) {
+ color.setColor(getResources().getColor(R.color.orange));
+ darkColor = getResources().getColor(R.color.dark_orange);
+ } else {
+ color.setColor(getResources().getColor(R.color.actionBarColor));
+ darkColor = getResources().getColor(R.color.actionBarColorDark);
+ }
+ getSupportActionBar().setBackgroundDrawable(color);
+
+ // Since Lollipop the status bar color may also be changed
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ getWindow().setStatusBarColor(darkColor);
+ } else {
+ final TransitionDrawable transition = (TransitionDrawable) getResources().getDrawable(
+ editMode ? R.drawable.start_edit_mode : R.drawable.stop_edit_mode);
+ transition.setCrossFadeEnabled(true);
+ getSupportActionBar().setBackgroundDrawable(transition);
+ transition.startTransition(200);
+
+ // Since Lollipop the status bar color may also be changed
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ final int colorFrom = getResources().getColor(editMode ? R.color.actionBarColorDark : R.color.dark_orange);
+ final int colorTo = getResources().getColor(!editMode ? R.color.actionBarColorDark : R.color.dark_orange);
+
+ final ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
+ anim.setDuration(200);
+ anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(final ValueAnimator animation) {
+ getWindow().setStatusBarColor((Integer) animation.getAnimatedValue());
+ }
+ });
+ anim.start();
+ }
+
+ if (mSlider != null && editMode) {
+ mSlider.closePane();
+ }
+ }
+ }
+
+}
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
new file mode 100644
index 000000000..32c7c41f7
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTButtonAdapter.java
@@ -0,0 +1,117 @@
+/*
+ * 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 no.nordicsemi.android.nrftoolbox.R;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+
+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 boolean mEditMode;
+
+ public UARTButtonAdapter(final Context context) {
+ mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ mIcons = new int[9];
+ mEnableFlags = new boolean[9];
+ }
+
+ public void setEditMode(final boolean editMode) {
+ mEditMode = 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();
+ }
+
+ @Override
+ public int getCount() {
+ return mIcons.length;
+ }
+
+ @Override
+ public Object getItem(final int position) {
+ return mIcons[position];
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return mEditMode || mEnableFlags[position];
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ view = inflater.inflate(R.layout.feature_uart_button, parent, false);
+ }
+ view.setEnabled(isEnabled(position));
+ view.setActivated(mEditMode);
+
+ // Update image
+ final ImageView image = (ImageView) view;
+ final int icon = mIcons[position];
+ if (mEnableFlags[position] && icon != -1) {
+ image.setImageResource(R.drawable.uart_button);
+ image.setImageLevel(icon);
+ } else
+ image.setImageDrawable(null);
+
+ return view;
+ }
+}
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
new file mode 100644
index 000000000..852ec6ac5
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTControlFragment.java
@@ -0,0 +1,137 @@
+/*
+ * 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 no.nordicsemi.android.nrftoolbox.R;
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.GridView;
+
+public class UARTControlFragment extends Fragment implements GridView.OnItemClickListener {
+ private final static String TAG = "UARTControlFragment";
+ private final static String SIS_EDIT_MODE = "sis_edit_mode";
+
+ private ControlFragmentListener mListener;
+ private SharedPreferences mPreferences;
+ 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);
+
+ try {
+ mListener = (ControlFragmentListener) activity;
+ } 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 onSaveInstanceState(final Bundle outState) {
+ outState.putBoolean(SIS_EDIT_MODE, mEditMode);
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
+ 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.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);
+ dialog.show(getChildFragmentManager(), null);
+ } else {
+ final UARTInterface uart = (UARTInterface) getActivity();
+ uart.send(mPreferences.getString(UARTButtonAdapter.PREFS_BUTTON_COMMAND + position, ""));
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(mEditMode ? R.menu.uart_menu_config : R.menu.uart_menu, menu);
+ }
+
+ @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 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
new file mode 100644
index 000000000..b509f437f
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTEditDialog.java
@@ -0,0 +1,169 @@
+/*
+ * 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 no.nordicsemi.android.nrftoolbox.R;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.GridView;
+import android.widget.ImageView;
+
+public class UARTEditDialog extends DialogFragment implements View.OnClickListener, GridView.OnItemClickListener {
+ private final static String TAG = "UARTEditDialog";
+ private final static String ARG_INDEX = "index";
+ private int mActiveIcon;
+
+ private EditText mField;
+ private CheckBox mCheckBox;
+ private IconAdapter mIconAdapter;
+
+ public static UARTEditDialog getInstance(final int index) {
+ final UARTEditDialog fragment = new UARTEditDialog();
+
+ final Bundle args = new Bundle();
+ args.putInt(ARG_INDEX, index);
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @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);
+
+ // Create view
+ final View view = inflater.inflate(R.layout.feature_uart_dialog_edit, null);
+ final EditText field = mField = (EditText) view.findViewById(R.id.field);
+ final GridView grid = (GridView) view.findViewById(R.id.grid);
+ final CheckBox checkBox = mCheckBox = (CheckBox) view.findViewById(R.id.active);
+ checkBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
+ field.setEnabled(isChecked);
+ grid.setEnabled(isChecked);
+ if (mIconAdapter != null)
+ mIconAdapter.notifyDataSetChanged();
+ }
+ });
+
+ field.setText(command);
+ field.setEnabled(active);
+ checkBox.setChecked(active);
+ grid.setOnItemClickListener(this);
+ grid.setEnabled(active);
+ grid.setAdapter(mIconAdapter = new IconAdapter());
+
+ // As we want to have some validation we can't user the DialogInterface.OnClickListener as it's always dismissing the dialog.
+ final AlertDialog dialog = new AlertDialog.Builder(getActivity()).setCancelable(false).setTitle(R.string.uart_edit_title).setPositiveButton(R.string.ok, null)
+ .setNegativeButton(R.string.cancel, null).setView(view).show();
+ final Button okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ okButton.setOnClickListener(this);
+ return dialog;
+ }
+
+ @Override
+ public void onClick(final View v) {
+ final boolean active = mCheckBox.isChecked();
+ final String command = mField.getText().toString();
+ if (active && TextUtils.isEmpty(command)) {
+ mField.setError(getString(R.string.uart_edit_command_error));
+ return;
+ }
+ mField.setError(null);
+
+ // Save values
+ 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.commit();
+
+ dismiss();
+ final UARTControlFragment parent = (UARTControlFragment) getParentFragment();
+ parent.onConfigurationChanged();
+ }
+
+ @Override
+ public void onItemClick(final AdapterView> parent, final View view, final int position, final long id) {
+ mActiveIcon = position;
+ mIconAdapter.notifyDataSetChanged();
+ }
+
+ private class IconAdapter extends BaseAdapter {
+ private final int SIZE = 20;
+
+ @Override
+ public int getCount() {
+ return SIZE;
+ }
+
+ @Override
+ public Object getItem(final int position) {
+ return position;
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = LayoutInflater.from(getActivity()).inflate(R.layout.feature_uart_dialog_edit_icon, parent, false);
+ }
+ final ImageView image = (ImageView) view;
+ image.setImageLevel(position);
+ image.setActivated(position == mActiveIcon && mCheckBox.isChecked());
+ return view;
+ }
+
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTInterface.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTInterface.java
new file mode 100644
index 000000000..ea3c19340
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTInterface.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+
+public interface UARTInterface {
+
+ public void send(final String text);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLocalLogContentProvider.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLocalLogContentProvider.java
new file mode 100644
index 000000000..ad23e4eb3
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLocalLogContentProvider.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;
+
+import no.nordicsemi.android.log.localprovider.LocalLogContentProvider;
+import android.net.Uri;
+
+public class UARTLocalLogContentProvider extends LocalLogContentProvider {
+ /** The authority for the contacts provider. */
+ public final static String AUTHORITY = "no.nordicsemi.android.nrftoolbox.uart.log";
+ /** A content:// style uri to the authority for the log provider. */
+ public final static Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ @Override
+ protected Uri getAuthorityUri() {
+ return AUTHORITY_URI;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogAdapter.java
new file mode 100644
index 000000000..701e05327
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * 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 java.util.Calendar;
+
+import no.nordicsemi.android.log.LogContract.Log.Level;
+import no.nordicsemi.android.nrftoolbox.R;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.util.SparseIntArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.TextView;
+
+public class UARTLogAdapter extends CursorAdapter {
+ private static final SparseIntArray mColors = new SparseIntArray();
+
+ static {
+ mColors.put(Level.DEBUG, 0xFF009CDE);
+ mColors.put(Level.VERBOSE, 0xFFB8B056);
+ mColors.put(Level.INFO, Color.BLACK);
+ mColors.put(Level.APPLICATION, 0xFF238C0F);
+ mColors.put(Level.WARNING, 0xFFD77926);
+ mColors.put(Level.ERROR, Color.RED);
+ }
+
+ public UARTLogAdapter(Context context) {
+ super(context, null, 0);
+ }
+
+ @Override
+ public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
+ final View view = LayoutInflater.from(context).inflate(R.layout.log_item, parent, false);
+
+ final ViewHolder holder = new ViewHolder();
+ holder.time = (TextView) view.findViewById(R.id.time);
+ holder.data = (TextView) view.findViewById(R.id.data);
+ view.setTag(holder);
+ return view;
+ }
+
+ @Override
+ public void bindView(final View view, final Context context, final Cursor cursor) {
+ final ViewHolder holder = (ViewHolder) view.getTag();
+ final Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(cursor.getLong(1 /* TIME */));
+ holder.time.setText(context.getString(R.string.log, calendar));
+
+ final int level = cursor.getInt(2 /* LEVEL */);
+ holder.data.setText(cursor.getString(3 /* DATA */));
+ holder.data.setTextColor(mColors.get(level));
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return false;
+ }
+
+ private class ViewHolder {
+ private TextView time;
+ private TextView data;
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogFragment.java
new file mode 100644
index 000000000..34e792733
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogFragment.java
@@ -0,0 +1,299 @@
+/*
+ * 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 no.nordicsemi.android.log.ILogSession;
+import no.nordicsemi.android.log.LogContract;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import android.app.ListFragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.Loader;
+import android.content.ServiceConnection;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.CursorAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class UARTLogFragment extends ListFragment implements LoaderCallbacks {
+ private static final String SIS_LOG_SCROLL_POSITION = "sis_scroll_position";
+ private static final int LOG_SCROLL_NULL = -1;
+ private static final int LOG_SCROLLED_TO_BOTTOM = -2;
+
+ private static final int LOG_REQUEST_ID = 1;
+ private static final String[] LOG_PROJECTION = { LogContract.Log._ID, LogContract.Log.TIME, LogContract.Log.LEVEL, LogContract.Log.DATA };
+
+ /** The service UART interface that may be used to send data to the target. */
+ private UARTInterface mUARTInterface;
+ /** The adapter used to populate the list with log entries. */
+ private CursorAdapter mLogAdapter;
+ /** The log session created to log events related with the target device. */
+ private ILogSession mLogSession;
+
+ private EditText mField;
+ private Button mSendButton;
+
+ /** The last list view position. */
+ private int mLogScrollPosition;
+
+ /**
+ * The receiver that listens for {@link BleProfileService#BROADCAST_CONNECTION_STATE} action.
+ */
+ private final BroadcastReceiver mCommonBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ // This receiver listens only for the BleProfileService.BROADCAST_CONNECTION_STATE action, no need to check it.
+
+ final int state = intent.getIntExtra(BleProfileService.EXTRA_CONNECTION_STATE, BleProfileService.STATE_DISCONNECTED);
+
+ switch (state) {
+ case BleProfileService.STATE_CONNECTED: {
+ onDeviceConnected();
+ break;
+ }
+ case BleProfileService.STATE_DISCONNECTED: {
+ onDeviceDisconnected();
+ break;
+ }
+ case BleProfileService.STATE_CONNECTING:
+ case BleProfileService.STATE_DISCONNECTING:
+ // current implementation does nothing in this states
+ default:
+ // there should be no other actions
+ break;
+
+ }
+ }
+ };
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(final ComponentName name, final IBinder service) {
+ final UARTService.UARTBinder bleService = (UARTService.UARTBinder) service;
+ mUARTInterface = bleService;
+ mLogSession = bleService.getLogSession();
+
+ // Start the loader
+ if (mLogSession != null) {
+ getLoaderManager().restartLoader(LOG_REQUEST_ID, null, UARTLogFragment.this);
+ }
+
+ // and notify user if device is connected
+ if (bleService.isConnected())
+ onDeviceConnected();
+ }
+
+ @Override
+ public void onServiceDisconnected(final ComponentName name) {
+ onDeviceDisconnected();
+ mUARTInterface = null;
+ }
+ };
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mCommonBroadcastReceiver, makeIntentFilter());
+
+ // Load the last log list view scroll position
+ if (savedInstanceState != null) {
+ mLogScrollPosition = savedInstanceState.getInt(SIS_LOG_SCROLL_POSITION);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ /*
+ * If the service has not been started before the following lines will not start it. However, if it's running, the Activity will be binded to it
+ * and notified via mServiceConnection.
+ */
+ final Intent service = new Intent(getActivity(), UARTService.class);
+ getActivity().bindService(service, mServiceConnection, 0); // we pass 0 as a flag so the service will not be created if not exists
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ try {
+ getActivity().unbindService(mServiceConnection);
+ mUARTInterface = null;
+ } catch (final IllegalArgumentException e) {
+ // do nothing, we were not connected to the sensor
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ // Save the last log list view scroll position
+ final ListView list = getListView();
+ final boolean scrolledToBottom = list.getCount() > 0 && list.getLastVisiblePosition() == list.getCount() - 1;
+ outState.putInt(SIS_LOG_SCROLL_POSITION, scrolledToBottom ? LOG_SCROLLED_TO_BOTTOM : list.getFirstVisiblePosition());
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mCommonBroadcastReceiver);
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.fragment_feature_uart_log, container, false);
+
+ final EditText field = mField = (EditText) view.findViewById(R.id.field);
+ field.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_SEND) {
+ onSendClicked();
+ return true;
+ }
+ return false;
+ }
+ });
+
+ final Button sendButton = mSendButton = (Button) view.findViewById(R.id.action_send);
+ sendButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ onSendClicked();
+ }
+ });
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(final View view, final Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ // Create the log adapter, initially with null cursor
+ mLogAdapter = new UARTLogAdapter(getActivity());
+ setListAdapter(mLogAdapter);
+ }
+
+ @Override
+ public Loader onCreateLoader(final int id, final Bundle args) {
+ switch (id) {
+ case LOG_REQUEST_ID: {
+ return new CursorLoader(getActivity(), mLogSession.getSessionEntriesUri(), LOG_PROJECTION, null, null, LogContract.Log.TIME);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(final Loader loader, final Cursor data) {
+ // Here we have to restore the old saved scroll position, or scroll to the bottom if before adding new events it was scrolled to the bottom.
+ final ListView list = getListView();
+ final int position = mLogScrollPosition;
+ final boolean scrolledToBottom = position == LOG_SCROLLED_TO_BOTTOM || (list.getCount() > 0 && list.getLastVisiblePosition() == list.getCount() - 1);
+
+ mLogAdapter.swapCursor(data);
+
+ if (position > LOG_SCROLL_NULL) {
+ list.setSelectionFromTop(position, 0);
+ } else {
+ if (scrolledToBottom)
+ list.setSelection(list.getCount() - 1);
+ }
+ mLogScrollPosition = LOG_SCROLL_NULL;
+ }
+
+ @Override
+ public void onLoaderReset(final Loader loader) {
+ mLogAdapter.swapCursor(null);
+ }
+
+ private void onSendClicked() {
+ final String text = mField.getText().toString();
+
+ mUARTInterface.send(text);
+
+ mField.setText(null);
+ mField.requestFocus();
+ }
+
+ /**
+ * Method called when user selected a device on the scanner dialog after the service has been started.
+ * Here we may bind this fragment to it.
+ */
+ public void onServiceStarted() {
+ // The service has been started, bind to it
+ final Intent service = new Intent(getActivity(), UARTService.class);
+ getActivity().bindService(service, mServiceConnection, 0);
+ }
+
+ /**
+ * This method is called when user closes the pane in horizontal orientation. The EditText is no longer visible so we need to close the soft keyboard here.
+ */
+ public void onFragmentHidden() {
+ InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mField.getWindowToken(), 0);
+ }
+
+ /**
+ * Method called when the target device has connected.
+ */
+ protected void onDeviceConnected() {
+ mField.setEnabled(true);
+ mSendButton.setEnabled(true);
+ }
+
+ /**
+ * Method called when user disconnected from the target UART device or the connection was lost.
+ */
+ protected void onDeviceDisconnected() {
+ mField.setEnabled(false);
+ mSendButton.setEnabled(false);
+ }
+
+ private static IntentFilter makeIntentFilter() {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(BleProfileService.BROADCAST_CONNECTION_STATE);
+ return intentFilter;
+ }
+}
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
new file mode 100644
index 000000000..435db4973
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManager.java
@@ -0,0 +1,187 @@
+/*
+ * 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 java.util.List;
+import java.util.UUID;
+
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+public class UARTManager implements BleManager {
+ private final static String TAG = "UARTManager";
+
+ private final static UUID UART_SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
+ /** TX characteristic */
+ private final static UUID UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
+ /** RX characteristic */
+ private final static UUID UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
+ /** Client configuration descriptor that will allow us to enable notifications and indications */
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+ private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
+ private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
+ private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor";
+
+ private BluetoothGattCharacteristic mTXCharacteristic, mRXCharacteristic;
+
+ private UARTManagerCallbacks mCallbacks;
+ private BluetoothGatt mBluetoothGatt;
+ private final Context mContext;
+
+ public UARTManager(final Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void setGattCallbacks(final UARTManagerCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void connect(final Context context, final BluetoothDevice device) {
+ if (mBluetoothGatt == null) {
+ mBluetoothGatt = device.connectGatt(mContext, true, mGattCallback);
+ } else {
+ mBluetoothGatt.connect();
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ }
+ }
+
+ public void send(final String text) {
+ if (mTXCharacteristic != null) {
+ mTXCharacteristic.setValue(text);
+ mBluetoothGatt.writeCharacteristic(mTXCharacteristic);
+ }
+ }
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
+ */
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ DebugLogger.d(TAG, "Device connected");
+ mBluetoothGatt.discoverServices();
+ //This will send callback to RSCActivity when device get connected
+ mCallbacks.onDeviceConnected();
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ DebugLogger.d(TAG, "Device disconnected");
+
+ mCallbacks.onDeviceDisconnected();
+ closeBluetoothGatt();
+ }
+ } else {
+ mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ final List services = gatt.getServices();
+ for (BluetoothGattService service : services) {
+ if (service.getUuid().equals(UART_SERVICE_UUID)) {
+ DebugLogger.d(TAG, "UART service is found");
+ mTXCharacteristic = service.getCharacteristic(UART_TX_CHARACTERISTIC_UUID);
+ mRXCharacteristic = service.getCharacteristic(UART_RX_CHARACTERISTIC_UUID);
+ }
+ }
+ if (mTXCharacteristic == null || mRXCharacteristic == null) {
+ mCallbacks.onDeviceNotSupported();
+ gatt.disconnect();
+ } else {
+ mCallbacks.onServicesDiscovered(false /* more characteristics not supported */);
+
+ // We have discovered services, let's start notifications on RX characteristics
+ enableRXNotification(gatt);
+ }
+ } else {
+ mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
+ }
+ }
+
+ @Override
+ public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ // do nothing
+ } else {
+ mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ final String data = characteristic.getStringValue(0);
+ mCallbacks.onDataSent(data);
+ } else {
+ mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
+ final String data = characteristic.getStringValue(0);
+ mCallbacks.onDataReceived(data);
+ }
+ };
+
+ /**
+ * Enabling notification on RX Characteristic
+ */
+ private void enableRXNotification(final BluetoothGatt gatt) {
+ gatt.setCharacteristicNotification(mRXCharacteristic, true);
+ final BluetoothGattDescriptor descriptor = mRXCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ }
+
+ @Override
+ public void closeBluetoothGatt() {
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ mTXCharacteristic = null;
+ mRXCharacteristic = null;
+ }
+ mCallbacks = null;
+ }
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManagerCallbacks.java
new file mode 100644
index 000000000..e80d212fb
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManagerCallbacks.java
@@ -0,0 +1,32 @@
+/*
+ * 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 no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
+
+public interface UARTManagerCallbacks extends BleManagerCallbacks {
+
+ public void onDataReceived(final String data);
+
+ public void onDataSent(final String data);
+}
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
new file mode 100644
index 000000000..4e762666e
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java
@@ -0,0 +1,188 @@
+/*
+ * 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 no.nordicsemi.android.log.ILogSession;
+import no.nordicsemi.android.log.Logger;
+import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
+import no.nordicsemi.android.nrftoolbox.R;
+import no.nordicsemi.android.nrftoolbox.profile.BleManager;
+import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class UARTService extends BleProfileService implements UARTManagerCallbacks {
+ public static final String BROADCAST_UART_TX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_TX";
+ public static final String BROADCAST_UART_RX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_RX";
+ public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.uart.EXTRA_DATA";
+
+ private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.uart.ACTION_DISCONNECT";
+
+ private final static int NOTIFICATION_ID = 349; // random
+ private final static int OPEN_ACTIVITY_REQ = 67; // random
+ private final static int DISCONNECT_REQ = 97; // random
+
+ private UARTManager mManager;
+ private boolean mBinded;
+
+ private final LocalBinder mBinder = new UARTBinder();
+
+ public class UARTBinder extends LocalBinder implements UARTInterface {
+ @Override
+ public void send(final String text) {
+ mManager.send(text);
+ }
+
+ @Override
+ public ILogSession getLogSession() {
+ return super.getLogSession();
+ }
+ }
+
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
+
+ @Override
+ protected BleManager initializeManager() {
+ return mManager = new UARTManager(this);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DISCONNECT);
+ registerReceiver(mDisconnectActionBroadcastReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
+ cancelNotification();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
+
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(final Intent intent) {
+ mBinded = true;
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onRebind(final Intent intent) {
+ mBinded = true;
+ // when the activity rebinds to the service, remove the notification
+ cancelNotification();
+ }
+
+ @Override
+ public boolean onUnbind(final Intent intent) {
+ mBinded = false;
+ // when the activity closes we need to show the notification that user is connected to the sensor
+ createNotifcation(R.string.uart_notification_connected_message, 0);
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onDataReceived(final String data) {
+ Logger.a(getLogSession(), "\"" + data + "\" received");
+
+ final Intent broadcast = new Intent(BROADCAST_UART_RX);
+ broadcast.putExtra(EXTRA_DATA, data);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onDataSent(String data) {
+ Logger.a(getLogSession(), "\"" + data + "\" sent");
+
+ final Intent broadcast = new Intent(BROADCAST_UART_TX);
+ broadcast.putExtra(EXTRA_DATA, data);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ /**
+ * Creates the notification
+ *
+ * @param messageResId
+ * message resource id. The message must have one String parameter,
+ * f.e. <string name="name">%s is connected</string>
+ * @param defaults
+ * signals that will be used to notify the user
+ */
+ private void createNotifcation(final int messageResId, final int defaults) {
+ final Intent parentIntent = new Intent(this, FeaturesActivity.class);
+ parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Intent targetIntent = new Intent(this, UARTActivity.class);
+
+ final Intent disconnect = new Intent(ACTION_DISCONNECT);
+ final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // both activities above have launchMode="singleTask" in the AndoridManifest.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);
+ 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);
+
+ final Notification notification = builder.build();
+ final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(NOTIFICATION_ID, notification);
+ }
+
+ /**
+ * Cancels the existing notification. If there is no active notification this method does nothing
+ */
+ private void cancelNotification() {
+ final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(NOTIFICATION_ID);
+ }
+
+ /**
+ * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
+ */
+ private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ Logger.i(getLogSession(), "Disconnect action pressed");
+ if (isConnected())
+ getBinder().disconnect();
+ else
+ stopSelf();
+ }
+ };
+
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/DebugLogger.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/DebugLogger.java
new file mode 100644
index 000000000..2c193a8e2
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/DebugLogger.java
@@ -0,0 +1,60 @@
+/*
+ * 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 no.nordicsemi.android.nrftoolbox.BuildConfig;
+import android.util.Log;
+
+public class DebugLogger {
+ public static void v(final String tag, final String text) {
+ if (BuildConfig.DEBUG)
+ Log.v(tag, text);
+ }
+
+ public static void d(String tag, String text) {
+ if (BuildConfig.DEBUG) {
+ Log.d(tag, text);
+ }
+ }
+
+ public static void i(final String tag, final String text) {
+ if (BuildConfig.DEBUG)
+ Log.i(tag, text);
+ }
+
+ public static void w(String tag, String text) {
+ if (BuildConfig.DEBUG) {
+ Log.w(tag, text);
+ }
+ }
+
+ public static void e(final String tag, final String text) {
+ if (BuildConfig.DEBUG)
+ Log.e(tag, text);
+ }
+
+ public static void wtf(String tag, String text) {
+ if (BuildConfig.DEBUG) {
+ Log.wtf(tag, text);
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetBoldTextView.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetBoldTextView.java
new file mode 100644
index 000000000..7e15f5333
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetBoldTextView.java
@@ -0,0 +1,56 @@
+/*
+ * 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 no.nordicsemi.android.nrftoolbox.R;
+import android.content.Context;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+public class TrebuchetBoldTextView extends TextView {
+
+ public TrebuchetBoldTextView(Context context) {
+ super(context);
+
+ init();
+ }
+
+ public TrebuchetBoldTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ init();
+ }
+
+ public TrebuchetBoldTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ init();
+ }
+
+ private void init() {
+ if (!isInEditMode()) {
+ final Typeface typeface = Typeface.createFromAsset(getContext().getAssets(), getContext().getString(R.string.font_path));
+ setTypeface(typeface);
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetTextView.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetTextView.java
new file mode 100644
index 000000000..ebbde29ee
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetTextView.java
@@ -0,0 +1,56 @@
+/*
+ * 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 no.nordicsemi.android.nrftoolbox.R;
+import android.content.Context;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+public class TrebuchetTextView extends TextView {
+
+ public TrebuchetTextView(Context context) {
+ super(context);
+
+ init();
+ }
+
+ public TrebuchetTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ init();
+ }
+
+ public TrebuchetTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ init();
+ }
+
+ private void init() {
+ if (!isInEditMode()) {
+ final Typeface typeface = Typeface.createFromAsset(getContext().getAssets(), getContext().getString(R.string.normal_font_path));
+ setTypeface(typeface);
+ }
+ }
+}
diff --git a/app/src/main/res/animator/click_animator.xml b/app/src/main/res/animator/click_animator.xml
new file mode 100644
index 000000000..7b316a4c6
--- /dev/null
+++ b/app/src/main/res/animator/click_animator.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/text_color_primary.xml b/app/src/main/res/color/text_color_primary.xml
new file mode 100644
index 000000000..3d054b88b
--- /dev/null
+++ b/app/src/main/res/color/text_color_primary.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/battery.png b/app/src/main/res/drawable-hdpi/battery.png
new file mode 100644
index 000000000..dd83f9bba
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/battery.png differ
diff --git a/app/src/main/res/drawable-hdpi/drawer_shadow.9.png b/app/src/main/res/drawable-hdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..236bff558
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/drawer_shadow.9.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_bluetooth.png b/app/src/main/res/drawable-hdpi/ic_action_bluetooth.png
new file mode 100644
index 000000000..a0a84a061
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_bluetooth.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_bpm_feature.png b/app/src/main/res/drawable-hdpi/ic_bpm_feature.png
new file mode 100644
index 000000000..875f8a268
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_bpm_feature.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_csc_feature.png b/app/src/main/res/drawable-hdpi/ic_csc_feature.png
new file mode 100644
index 000000000..8a105bd0e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_csc_feature.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_dfu_feature.png b/app/src/main/res/drawable-hdpi/ic_dfu_feature.png
new file mode 100644
index 000000000..db59fae82
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_dfu_feature.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_drawer.png b/app/src/main/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 000000000..bb40b7230
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_drawer.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_glucose_feature.png b/app/src/main/res/drawable-hdpi/ic_glucose_feature.png
new file mode 100644
index 000000000..9ea924397
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_glucose_feature.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_help.png b/app/src/main/res/drawable-hdpi/ic_help.png
new file mode 100644
index 000000000..459bed76c
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_help.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_hrs_feature.png b/app/src/main/res/drawable-hdpi/ic_hrs_feature.png
new file mode 100644
index 000000000..650a4da63
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_hrs_feature.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_hts_feature.png b/app/src/main/res/drawable-hdpi/ic_hts_feature.png
new file mode 100644
index 000000000..de2f119ef
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_hts_feature.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..628705f7a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_menu_about.png b/app/src/main/res/drawable-hdpi/ic_menu_about.png
new file mode 100644
index 000000000..c643fa54d
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_about.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_menu_settings.png b/app/src/main/res/drawable-hdpi/ic_menu_settings.png
new file mode 100644
index 000000000..3e4580e05
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_settings.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_permission_log.png b/app/src/main/res/drawable-hdpi/ic_permission_log.png
new file mode 100644
index 000000000..e45ea1fd9
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_permission_log.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_proximity_feature.png b/app/src/main/res/drawable-hdpi/ic_proximity_feature.png
new file mode 100644
index 000000000..5b80a8811
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_proximity_feature.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_rsc_feature.png b/app/src/main/res/drawable-hdpi/ic_rsc_feature.png
new file mode 100644
index 000000000..8e45929ec
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_rsc_feature.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_rssi_0_bar.png b/app/src/main/res/drawable-hdpi/ic_rssi_0_bar.png
new file mode 100644
index 000000000..40d094f34
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_rssi_0_bar.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_rssi_1_bar.png b/app/src/main/res/drawable-hdpi/ic_rssi_1_bar.png
new file mode 100644
index 000000000..72b6996ad
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_rssi_1_bar.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_rssi_2_bars.png b/app/src/main/res/drawable-hdpi/ic_rssi_2_bars.png
new file mode 100644
index 000000000..dfa10ec37
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_rssi_2_bars.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_rssi_3_bars.png b/app/src/main/res/drawable-hdpi/ic_rssi_3_bars.png
new file mode 100644
index 000000000..ae512dbcd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_rssi_3_bars.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_notify_csc.png b/app/src/main/res/drawable-hdpi/ic_stat_notify_csc.png
new file mode 100644
index 000000000..ddc2e2589
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_notify_csc.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_notify_dfu.png b/app/src/main/res/drawable-hdpi/ic_stat_notify_dfu.png
new file mode 100644
index 000000000..261078de2
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_notify_dfu.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_notify_hts.png b/app/src/main/res/drawable-hdpi/ic_stat_notify_hts.png
new file mode 100644
index 000000000..4fed0936c
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_notify_hts.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_notify_proximity.png b/app/src/main/res/drawable-hdpi/ic_stat_notify_proximity.png
new file mode 100644
index 000000000..af0b4549d
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_notify_proximity.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_notify_rsc.png b/app/src/main/res/drawable-hdpi/ic_stat_notify_rsc.png
new file mode 100644
index 000000000..14ed1632f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_notify_rsc.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_notify_uart.png b/app/src/main/res/drawable-hdpi/ic_stat_notify_uart.png
new file mode 100644
index 000000000..a08119382
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_notify_uart.png differ
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
new file mode 100644
index 000000000..0c8a94514
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/item_background_light_n.9.png 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
new file mode 100644
index 000000000..74947f36a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/item_background_light_p.9.png differ
diff --git a/app/src/main/res/drawable-hdpi/list_divider_holo_light.9.png b/app/src/main/res/drawable-hdpi/list_divider_holo_light.9.png
new file mode 100644
index 000000000..0279e17a1
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/list_divider_holo_light.9.png differ
diff --git a/app/src/main/res/drawable-hdpi/nordic_logo.png b/app/src/main/res/drawable-hdpi/nordic_logo.png
new file mode 100644
index 000000000..8b9fb39d9
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/nordic_logo.png differ
diff --git a/app/src/main/res/drawable-hdpi/proximity_lock_closed.png b/app/src/main/res/drawable-hdpi/proximity_lock_closed.png
new file mode 100644
index 000000000..65220ea49
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/proximity_lock_closed.png differ
diff --git a/app/src/main/res/drawable-hdpi/proximity_lock_open.png b/app/src/main/res/drawable-hdpi/proximity_lock_open.png
new file mode 100644
index 000000000..e2c0f280e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/proximity_lock_open.png differ
diff --git a/app/src/main/res/drawable-hdpi/shadow_l.png b/app/src/main/res/drawable-hdpi/shadow_l.png
new file mode 100644
index 000000000..b007003a9
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/shadow_l.png differ
diff --git a/app/src/main/res/drawable-hdpi/shadow_r.png b/app/src/main/res/drawable-hdpi/shadow_r.png
new file mode 100644
index 000000000..ae07b5937
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/shadow_r.png differ
diff --git a/app/src/main/res/drawable-land-xhdpi/background_image.jpg b/app/src/main/res/drawable-land-xhdpi/background_image.jpg
new file mode 100644
index 000000000..f8dfa38cd
Binary files /dev/null and b/app/src/main/res/drawable-land-xhdpi/background_image.jpg differ
diff --git a/app/src/main/res/drawable-land-xhdpi/nordic_logo.png b/app/src/main/res/drawable-land-xhdpi/nordic_logo.png
new file mode 100644
index 000000000..24a9d4e76
Binary files /dev/null and b/app/src/main/res/drawable-land-xhdpi/nordic_logo.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..8f8df29d5
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-sw600dp-xhdpi/nordic_logo.png b/app/src/main/res/drawable-sw600dp-xhdpi/nordic_logo.png
new file mode 100644
index 000000000..ec3104588
Binary files /dev/null and b/app/src/main/res/drawable-sw600dp-xhdpi/nordic_logo.png differ
diff --git a/app/src/main/res/drawable-v21/button.xml b/app/src/main/res/drawable-v21/button.xml
new file mode 100644
index 000000000..4ce2cf21c
--- /dev/null
+++ b/app/src/main/res/drawable-v21/button.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/ic_feature_bg.xml b/app/src/main/res/drawable-v21/ic_feature_bg.xml
new file mode 100644
index 000000000..8b53267dd
--- /dev/null
+++ b/app/src/main/res/drawable-v21/ic_feature_bg.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-v21/uart_button_activated.xml b/app/src/main/res/drawable-v21/uart_button_activated.xml
new file mode 100644
index 000000000..e00d0b72f
--- /dev/null
+++ b/app/src/main/res/drawable-v21/uart_button_activated.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-v21/uart_button_background.xml b/app/src/main/res/drawable-v21/uart_button_background.xml
new file mode 100644
index 000000000..c2a17690e
--- /dev/null
+++ b/app/src/main/res/drawable-v21/uart_button_background.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/uart_button_normal.xml b/app/src/main/res/drawable-v21/uart_button_normal.xml
new file mode 100644
index 000000000..e754af7a8
--- /dev/null
+++ b/app/src/main/res/drawable-v21/uart_button_normal.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-xhdpi/app_drive.png b/app/src/main/res/drawable-xhdpi/app_drive.png
new file mode 100644
index 000000000..e64055116
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/app_drive.png differ
diff --git a/app/src/main/res/drawable-xhdpi/app_file_manager.png b/app/src/main/res/drawable-xhdpi/app_file_manager.png
new file mode 100644
index 000000000..1f3c55bc4
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/app_file_manager.png differ
diff --git a/app/src/main/res/drawable-xhdpi/app_google_play.png b/app/src/main/res/drawable-xhdpi/app_google_play.png
new file mode 100644
index 000000000..14e0258a9
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/app_google_play.png differ
diff --git a/app/src/main/res/drawable-xhdpi/app_total_commander.png b/app/src/main/res/drawable-xhdpi/app_total_commander.png
new file mode 100644
index 000000000..406c6cc1e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/app_total_commander.png differ
diff --git a/app/src/main/res/drawable-xhdpi/background_image.jpg b/app/src/main/res/drawable-xhdpi/background_image.jpg
new file mode 100644
index 000000000..2a1742155
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/background_image.jpg differ
diff --git a/app/src/main/res/drawable-xhdpi/btn_default_focused_holo_light.9.png b/app/src/main/res/drawable-xhdpi/btn_default_focused_holo_light.9.png
new file mode 100644
index 000000000..73488f35c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/btn_default_focused_holo_light.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png b/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png
new file mode 100644
index 000000000..fabe9d965
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_bluetooth.png b/app/src/main/res/drawable-xhdpi/ic_action_bluetooth.png
new file mode 100644
index 000000000..40451ca66
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_bluetooth.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_bpm_feature.png b/app/src/main/res/drawable-xhdpi/ic_bpm_feature.png
new file mode 100644
index 000000000..681e26178
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_bpm_feature.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_csc_feature.png b/app/src/main/res/drawable-xhdpi/ic_csc_feature.png
new file mode 100644
index 000000000..ba5e6243c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_csc_feature.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_dfu_feature.png b/app/src/main/res/drawable-xhdpi/ic_dfu_feature.png
new file mode 100644
index 000000000..d7065e056
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_dfu_feature.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer.png b/app/src/main/res/drawable-xhdpi/ic_drawer.png
new file mode 100644
index 000000000..0f04c4972
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_drawer.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_glucose_feature.png b/app/src/main/res/drawable-xhdpi/ic_glucose_feature.png
new file mode 100644
index 000000000..99ab2f537
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_glucose_feature.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_help.png b/app/src/main/res/drawable-xhdpi/ic_help.png
new file mode 100644
index 000000000..0e67d7c12
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_help.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_hrs_feature.png b/app/src/main/res/drawable-xhdpi/ic_hrs_feature.png
new file mode 100644
index 000000000..d28b50d48
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_hrs_feature.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_hts_feature.png b/app/src/main/res/drawable-xhdpi/ic_hts_feature.png
new file mode 100644
index 000000000..c86d7091a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_hts_feature.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..b253a292d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_mcp_feature_fg.png b/app/src/main/res/drawable-xhdpi/ic_mcp_feature_fg.png
new file mode 100644
index 000000000..f57497a6e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_mcp_feature_fg.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_about.png b/app/src/main/res/drawable-xhdpi/ic_menu_about.png
new file mode 100644
index 000000000..e4be458dd
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu_about.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_settings.png b/app/src/main/res/drawable-xhdpi/ic_menu_settings.png
new file mode 100644
index 000000000..09b014834
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu_settings.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_permission_log.png b/app/src/main/res/drawable-xhdpi/ic_permission_log.png
new file mode 100644
index 000000000..95708234a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_permission_log.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_proximity_feature.png b/app/src/main/res/drawable-xhdpi/ic_proximity_feature.png
new file mode 100644
index 000000000..d0e7de157
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_proximity_feature.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_rsc_feature.png b/app/src/main/res/drawable-xhdpi/ic_rsc_feature.png
new file mode 100644
index 000000000..3f81e0bbc
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_rsc_feature.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_notify_csc.png b/app/src/main/res/drawable-xhdpi/ic_stat_notify_csc.png
new file mode 100644
index 000000000..d0f34662a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_notify_csc.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_notify_dfu.png b/app/src/main/res/drawable-xhdpi/ic_stat_notify_dfu.png
new file mode 100644
index 000000000..6009fd1bd
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_notify_dfu.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_notify_hts.png b/app/src/main/res/drawable-xhdpi/ic_stat_notify_hts.png
new file mode 100644
index 000000000..a33559340
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_notify_hts.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_notify_proximity.png b/app/src/main/res/drawable-xhdpi/ic_stat_notify_proximity.png
new file mode 100644
index 000000000..a43b04f80
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_notify_proximity.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_notify_rsc.png b/app/src/main/res/drawable-xhdpi/ic_stat_notify_rsc.png
new file mode 100644
index 000000000..2ab168938
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_notify_rsc.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_notify_uart.png b/app/src/main/res/drawable-xhdpi/ic_stat_notify_uart.png
new file mode 100644
index 000000000..4255d2b2f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_notify_uart.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_1.png b/app/src/main/res/drawable-xhdpi/ic_uart_1.png
new file mode 100644
index 000000000..d737be597
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_1.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_1_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_1_small.png
new file mode 100644
index 000000000..cf8880f83
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_1_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_2.png b/app/src/main/res/drawable-xhdpi/ic_uart_2.png
new file mode 100644
index 000000000..db2432c1b
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_2.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_2_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_2_small.png
new file mode 100644
index 000000000..855a88ccb
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_2_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_3.png b/app/src/main/res/drawable-xhdpi/ic_uart_3.png
new file mode 100644
index 000000000..7664f4f44
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_3.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_3_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_3_small.png
new file mode 100644
index 000000000..6c40e487c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_3_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_4.png b/app/src/main/res/drawable-xhdpi/ic_uart_4.png
new file mode 100644
index 000000000..1f00c16cf
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_4.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_4_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_4_small.png
new file mode 100644
index 000000000..68b32fd90
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_4_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_5.png b/app/src/main/res/drawable-xhdpi/ic_uart_5.png
new file mode 100644
index 000000000..190332e13
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_5.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_5_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_5_small.png
new file mode 100644
index 000000000..192fde118
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_5_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_6.png b/app/src/main/res/drawable-xhdpi/ic_uart_6.png
new file mode 100644
index 000000000..7b8879c30
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_6.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_6_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_6_small.png
new file mode 100644
index 000000000..1aa1c473d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_6_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_7.png b/app/src/main/res/drawable-xhdpi/ic_uart_7.png
new file mode 100644
index 000000000..3e12930e3
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_7.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_7_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_7_small.png
new file mode 100644
index 000000000..03293e528
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_7_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_8.png b/app/src/main/res/drawable-xhdpi/ic_uart_8.png
new file mode 100644
index 000000000..80d2911ba
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_8.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_8_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_8_small.png
new file mode 100644
index 000000000..3a25ec410
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_8_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_9.png b/app/src/main/res/drawable-xhdpi/ic_uart_9.png
new file mode 100644
index 000000000..3bf41969c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_9_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_9_small.png
new file mode 100644
index 000000000..d2290c54c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_9_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_about.png b/app/src/main/res/drawable-xhdpi/ic_uart_about.png
new file mode 100644
index 000000000..a7bdf347a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_about.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_about_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_about_small.png
new file mode 100644
index 000000000..3be315270
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_about_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_down.png b/app/src/main/res/drawable-xhdpi/ic_uart_down.png
new file mode 100644
index 000000000..51070821b
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_down.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_down_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_down_small.png
new file mode 100644
index 000000000..76937f57a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_down_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_feature.png b/app/src/main/res/drawable-xhdpi/ic_uart_feature.png
new file mode 100644
index 000000000..f17394420
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_feature.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_forward.png b/app/src/main/res/drawable-xhdpi/ic_uart_forward.png
new file mode 100644
index 000000000..cd5040e39
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_forward.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_forward_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_forward_small.png
new file mode 100644
index 000000000..fec20183a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_forward_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_left.png b/app/src/main/res/drawable-xhdpi/ic_uart_left.png
new file mode 100644
index 000000000..c8c63a944
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_left.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_left_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_left_small.png
new file mode 100644
index 000000000..ed8ac91de
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_left_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_pause.png b/app/src/main/res/drawable-xhdpi/ic_uart_pause.png
new file mode 100644
index 000000000..293f7127d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_pause.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_pause_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_pause_small.png
new file mode 100644
index 000000000..504389aa0
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_pause_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_play.png b/app/src/main/res/drawable-xhdpi/ic_uart_play.png
new file mode 100644
index 000000000..97ff9b077
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_play.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_play_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_play_small.png
new file mode 100644
index 000000000..7f709bbf1
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_play_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_rewind.png b/app/src/main/res/drawable-xhdpi/ic_uart_rewind.png
new file mode 100644
index 000000000..3e66f69b8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_rewind.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_rewind_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_rewind_small.png
new file mode 100644
index 000000000..27a2b9e73
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_rewind_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_right.png b/app/src/main/res/drawable-xhdpi/ic_uart_right.png
new file mode 100644
index 000000000..4ac16d068
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_right.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_right_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_right_small.png
new file mode 100644
index 000000000..5f304742f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_right_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_settings.png b/app/src/main/res/drawable-xhdpi/ic_uart_settings.png
new file mode 100644
index 000000000..fe5fec471
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_settings.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_settings_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_settings_small.png
new file mode 100644
index 000000000..999d0f0d8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_settings_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_stop.png b/app/src/main/res/drawable-xhdpi/ic_uart_stop.png
new file mode 100644
index 000000000..c86dbb158
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_stop.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_stop_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_stop_small.png
new file mode 100644
index 000000000..2b07de4c2
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_stop_small.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_up.png b/app/src/main/res/drawable-xhdpi/ic_uart_up.png
new file mode 100644
index 000000000..5411d8c99
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_up.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_uart_up_small.png b/app/src/main/res/drawable-xhdpi/ic_uart_up_small.png
new file mode 100644
index 000000000..60ac6b066
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_uart_up_small.png differ
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
new file mode 100644
index 000000000..0c8a94514
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/item_background_light_n.9.png 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
new file mode 100644
index 000000000..74947f36a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/item_background_light_p.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/list_divider_holo_light.9.png b/app/src/main/res/drawable-xhdpi/list_divider_holo_light.9.png
new file mode 100644
index 000000000..65061c0f4
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/list_divider_holo_light.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/nordic_logo.png b/app/src/main/res/drawable-xhdpi/nordic_logo.png
new file mode 100644
index 000000000..9cdca3d7d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/nordic_logo.png differ
diff --git a/app/src/main/res/drawable-xhdpi/nordic_logo_horiz.png b/app/src/main/res/drawable-xhdpi/nordic_logo_horiz.png
new file mode 100644
index 000000000..80552bc98
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/nordic_logo_horiz.png differ
diff --git a/app/src/main/res/drawable-xhdpi/proximity_lock_closed.png b/app/src/main/res/drawable-xhdpi/proximity_lock_closed.png
new file mode 100644
index 000000000..010a0508a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/proximity_lock_closed.png differ
diff --git a/app/src/main/res/drawable-xhdpi/proximity_lock_open.png b/app/src/main/res/drawable-xhdpi/proximity_lock_open.png
new file mode 100644
index 000000000..d4000316e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/proximity_lock_open.png differ
diff --git a/app/src/main/res/drawable-xhdpi/shadow_l.png b/app/src/main/res/drawable-xhdpi/shadow_l.png
new file mode 100644
index 000000000..b007003a9
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/shadow_l.png differ
diff --git a/app/src/main/res/drawable-xhdpi/shadow_r.png b/app/src/main/res/drawable-xhdpi/shadow_r.png
new file mode 100644
index 000000000..ae07b5937
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/shadow_r.png differ
diff --git a/app/src/main/res/drawable-xhdpi/zip.png b/app/src/main/res/drawable-xhdpi/zip.png
new file mode 100644
index 000000000..eb96135d4
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/zip.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/background_title.png b/app/src/main/res/drawable-xxhdpi/background_title.png
new file mode 100644
index 000000000..db5c4f5e8
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/background_title.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..d282a9af4
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable/app_file_browser.xml b/app/src/main/res/drawable/app_file_browser.xml
new file mode 100644
index 000000000..09b923883
--- /dev/null
+++ b/app/src/main/res/drawable/app_file_browser.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/background.xml b/app/src/main/res/drawable/background.xml
new file mode 100644
index 000000000..bb025afee
--- /dev/null
+++ b/app/src/main/res/drawable/background.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/button.xml b/app/src/main/res/drawable/button.xml
new file mode 100644
index 000000000..fe2238024
--- /dev/null
+++ b/app/src/main/res/drawable/button.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/button_n.xml b/app/src/main/res/drawable/button_n.xml
new file mode 100644
index 000000000..0b9032155
--- /dev/null
+++ b/app/src/main/res/drawable/button_n.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/button_p.xml b/app/src/main/res/drawable/button_p.xml
new file mode 100644
index 000000000..d79ca8aac
--- /dev/null
+++ b/app/src/main/res/drawable/button_p.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_feature_bg.xml b/app/src/main/res/drawable/ic_feature_bg.xml
new file mode 100644
index 000000000..b06fc4da6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_feature_bg.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_feature_bg_n.xml b/app/src/main/res/drawable/ic_feature_bg_n.xml
new file mode 100644
index 000000000..339a37ff9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_feature_bg_n.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_feature_bg_p.xml b/app/src/main/res/drawable/ic_feature_bg_p.xml
new file mode 100644
index 000000000..5e0aefcbb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_feature_bg_p.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_feature_small_bg.xml b/app/src/main/res/drawable/ic_feature_small_bg.xml
new file mode 100644
index 000000000..8134d3db4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_feature_small_bg.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_feature_small_bg_n.xml b/app/src/main/res/drawable/ic_feature_small_bg_n.xml
new file mode 100644
index 000000000..324d07db1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_feature_small_bg_n.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_feature_small_bg_p.xml b/app/src/main/res/drawable/ic_feature_small_bg_p.xml
new file mode 100644
index 000000000..99ecdf8bb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_feature_small_bg_p.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_mcp_feature_small.xml b/app/src/main/res/drawable/ic_mcp_feature_small.xml
new file mode 100644
index 000000000..01d2b05d4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_mcp_feature_small.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_rssi_bar.xml b/app/src/main/res/drawable/ic_rssi_bar.xml
new file mode 100644
index 000000000..e37a8b747
--- /dev/null
+++ b/app/src/main/res/drawable/ic_rssi_bar.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/item_background_light.xml b/app/src/main/res/drawable/item_background_light.xml
new file mode 100644
index 000000000..b1d87923b
--- /dev/null
+++ b/app/src/main/res/drawable/item_background_light.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/start_edit_mode.xml b/app/src/main/res/drawable/start_edit_mode.xml
new file mode 100644
index 000000000..de9d6192b
--- /dev/null
+++ b/app/src/main/res/drawable/start_edit_mode.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/stop_edit_mode.xml b/app/src/main/res/drawable/stop_edit_mode.xml
new file mode 100644
index 000000000..d5491ef8f
--- /dev/null
+++ b/app/src/main/res/drawable/stop_edit_mode.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/uart_button.xml b/app/src/main/res/drawable/uart_button.xml
new file mode 100644
index 000000000..0abfeb587
--- /dev/null
+++ b/app/src/main/res/drawable/uart_button.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/uart_button_background.xml b/app/src/main/res/drawable/uart_button_background.xml
new file mode 100644
index 000000000..3c1580e6c
--- /dev/null
+++ b/app/src/main/res/drawable/uart_button_background.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/uart_button_small.xml b/app/src/main/res/drawable/uart_button_small.xml
new file mode 100644
index 000000000..fb981d2dc
--- /dev/null
+++ b/app/src/main/res/drawable/uart_button_small.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/uart_dialog_button_background.xml b/app/src/main/res/drawable/uart_dialog_button_background.xml
new file mode 100644
index 000000000..fd4991fbe
--- /dev/null
+++ b/app/src/main/res/drawable/uart_dialog_button_background.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/activity_feature_bpm.xml b/app/src/main/res/layout-land/activity_feature_bpm.xml
new file mode 100644
index 000000000..3677f2456
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_feature_bpm.xml
@@ -0,0 +1,286 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/activity_feature_csc.xml b/app/src/main/res/layout-land/activity_feature_csc.xml
new file mode 100644
index 000000000..8dfc641d5
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_feature_csc.xml
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..170f27f7c
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_feature_dfu.xml
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..f1de4f62b
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_feature_gls.xml
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..9743b273a
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_feature_hrs.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/activity_feature_hts.xml b/app/src/main/res/layout-land/activity_feature_hts.xml
new file mode 100644
index 000000000..ef2e050f8
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_feature_hts.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/activity_feature_proximity.xml b/app/src/main/res/layout-land/activity_feature_proximity.xml
new file mode 100644
index 000000000..95bf1dbee
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_feature_proximity.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/activity_feature_rsc.xml b/app/src/main/res/layout-land/activity_feature_rsc.xml
new file mode 100644
index 000000000..df301c087
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_feature_rsc.xml
@@ -0,0 +1,311 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/activity_feature_uart.xml b/app/src/main/res/layout-land/activity_feature_uart.xml
new file mode 100644
index 000000000..a10aa6c8a
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_feature_uart.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..25e6c5c40
--- /dev/null
+++ b/app/src/main/res/layout-land/feature_uart_button.xml
@@ -0,0 +1,29 @@
+
+
+
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
new file mode 100644
index 000000000..73f6dc6fb
--- /dev/null
+++ b/app/src/main/res/layout-land/fragment_feature_uart_control.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..71c07133f
--- /dev/null
+++ b/app/src/main/res/layout-sw600dp-land/activity_feature_bpm.xml
@@ -0,0 +1,288 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 000000000..b157b0444
--- /dev/null
+++ b/app/src/main/res/layout-sw600dp-land/activity_feature_csc.xml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..5960265be
--- /dev/null
+++ b/app/src/main/res/layout-sw600dp-land/activity_feature_dfu.xml
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..5a79cf0aa
--- /dev/null
+++ b/app/src/main/res/layout-sw600dp-land/activity_feature_hrs.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 000000000..50ce8af14
--- /dev/null
+++ b/app/src/main/res/layout-sw600dp-land/feature_uart_button.xml
@@ -0,0 +1,28 @@
+
+
+
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
new file mode 100644
index 000000000..50cc72b80
--- /dev/null
+++ b/app/src/main/res/layout-sw720dp-land/activity_feature_rsc.xml
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-v21/feature_icon.xml b/app/src/main/res/layout-v21/feature_icon.xml
new file mode 100644
index 000000000..e295b997b
--- /dev/null
+++ b/app/src/main/res/layout-v21/feature_icon.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-v21/fragment_device_selection.xml b/app/src/main/res/layout-v21/fragment_device_selection.xml
new file mode 100644
index 000000000..ab7becf61
--- /dev/null
+++ b/app/src/main/res/layout-v21/fragment_device_selection.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..b15bfd6a7
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_bpm.xml
@@ -0,0 +1,275 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_feature_csc.xml b/app/src/main/res/layout/activity_feature_csc.xml
new file mode 100644
index 000000000..b157b0444
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_csc.xml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..bad8c83c3
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_dfu.xml
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..4be594a77
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_gls.xml
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..86e1d8a55
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_gls_item.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_feature_gls_subitem.xml b/app/src/main/res/layout/activity_feature_gls_subitem.xml
new file mode 100644
index 000000000..6e9e29b66
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_gls_subitem.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_feature_hrs.xml b/app/src/main/res/layout/activity_feature_hrs.xml
new file mode 100644
index 000000000..4dd41d129
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_hrs.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_feature_hts.xml b/app/src/main/res/layout/activity_feature_hts.xml
new file mode 100644
index 000000000..ef2e050f8
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_hts.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_feature_proximity.xml b/app/src/main/res/layout/activity_feature_proximity.xml
new file mode 100644
index 000000000..c3ef1f447
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_proximity.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..50cc72b80
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_rsc.xml
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_feature_uart.xml b/app/src/main/res/layout/activity_feature_uart.xml
new file mode 100644
index 000000000..76d9797b4
--- /dev/null
+++ b/app/src/main/res/layout/activity_feature_uart.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..272ae0199
--- /dev/null
+++ b/app/src/main/res/layout/activity_features.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_splashscreen.xml b/app/src/main/res/layout/activity_splashscreen.xml
new file mode 100644
index 000000000..687d33376
--- /dev/null
+++ b/app/src/main/res/layout/activity_splashscreen.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..9d91dae47
--- /dev/null
+++ b/app/src/main/res/layout/app_file_browser.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/app_file_browser_item.xml b/app/src/main/res/layout/app_file_browser_item.xml
new file mode 100644
index 000000000..662e638d7
--- /dev/null
+++ b/app/src/main/res/layout/app_file_browser_item.xml
@@ -0,0 +1,35 @@
+
+
diff --git a/app/src/main/res/layout/device_list_empty.xml b/app/src/main/res/layout/device_list_empty.xml
new file mode 100644
index 000000000..c4a12eb7c
--- /dev/null
+++ b/app/src/main/res/layout/device_list_empty.xml
@@ -0,0 +1,27 @@
+
+
+
diff --git a/app/src/main/res/layout/device_list_row.xml b/app/src/main/res/layout/device_list_row.xml
new file mode 100644
index 000000000..b9e23f3cb
--- /dev/null
+++ b/app/src/main/res/layout/device_list_row.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..2bdaa6360
--- /dev/null
+++ b/app/src/main/res/layout/device_list_title.xml
@@ -0,0 +1,26 @@
+
+
+
diff --git a/app/src/main/res/layout/dialog_about_text.xml b/app/src/main/res/layout/dialog_about_text.xml
new file mode 100644
index 000000000..fe6a45aec
--- /dev/null
+++ b/app/src/main/res/layout/dialog_about_text.xml
@@ -0,0 +1,26 @@
+
+
+
diff --git a/app/src/main/res/layout/drawer.xml b/app/src/main/res/layout/drawer.xml
new file mode 100644
index 000000000..6f12bccaf
--- /dev/null
+++ b/app/src/main/res/layout/drawer.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/drawer_plugin.xml b/app/src/main/res/layout/drawer_plugin.xml
new file mode 100644
index 000000000..b3eba0ace
--- /dev/null
+++ b/app/src/main/res/layout/drawer_plugin.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..799ea5a71
--- /dev/null
+++ b/app/src/main/res/layout/expandable_list_content.xml
@@ -0,0 +1,24 @@
+
+
+
diff --git a/app/src/main/res/layout/feature_icon.xml b/app/src/main/res/layout/feature_icon.xml
new file mode 100644
index 000000000..245f7fe07
--- /dev/null
+++ b/app/src/main/res/layout/feature_icon.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/feature_uart_button.xml b/app/src/main/res/layout/feature_uart_button.xml
new file mode 100644
index 000000000..50ce8af14
--- /dev/null
+++ b/app/src/main/res/layout/feature_uart_button.xml
@@ -0,0 +1,28 @@
+
+
+
diff --git a/app/src/main/res/layout/feature_uart_dialog_edit.xml b/app/src/main/res/layout/feature_uart_dialog_edit.xml
new file mode 100644
index 000000000..37cae0d9f
--- /dev/null
+++ b/app/src/main/res/layout/feature_uart_dialog_edit.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..139c72390
--- /dev/null
+++ b/app/src/main/res/layout/feature_uart_dialog_edit_icon.xml
@@ -0,0 +1,28 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_device_selection.xml b/app/src/main/res/layout/fragment_device_selection.xml
new file mode 100644
index 000000000..262ad8edf
--- /dev/null
+++ b/app/src/main/res/layout/fragment_device_selection.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..cd23cb66c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_feature_uart_control.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..60a01899c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_feature_uart_log.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..201b79d9b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_zip_info.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..0734866ff
--- /dev/null
+++ b/app/src/main/res/layout/log_item.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu-port/uart_menu.xml b/app/src/main/res/menu-port/uart_menu.xml
new file mode 100644
index 000000000..b828bf6e9
--- /dev/null
+++ b/app/src/main/res/menu-port/uart_menu.xml
@@ -0,0 +1,34 @@
+
+
diff --git a/app/src/main/res/menu-port/uart_menu_config.xml b/app/src/main/res/menu-port/uart_menu_config.xml
new file mode 100644
index 000000000..9325bf564
--- /dev/null
+++ b/app/src/main/res/menu-port/uart_menu_config.xml
@@ -0,0 +1,34 @@
+
+
diff --git a/app/src/main/res/menu/csc_menu.xml b/app/src/main/res/menu/csc_menu.xml
new file mode 100644
index 000000000..594e00511
--- /dev/null
+++ b/app/src/main/res/menu/csc_menu.xml
@@ -0,0 +1,37 @@
+
+
diff --git a/app/src/main/res/menu/dfu_menu.xml b/app/src/main/res/menu/dfu_menu.xml
new file mode 100644
index 000000000..594e00511
--- /dev/null
+++ b/app/src/main/res/menu/dfu_menu.xml
@@ -0,0 +1,37 @@
+
+
diff --git a/app/src/main/res/menu/gls_more.xml b/app/src/main/res/menu/gls_more.xml
new file mode 100644
index 000000000..a1ccf4e86
--- /dev/null
+++ b/app/src/main/res/menu/gls_more.xml
@@ -0,0 +1,40 @@
+
+
diff --git a/app/src/main/res/menu/help.xml b/app/src/main/res/menu/help.xml
new file mode 100644
index 000000000..fe54e57f6
--- /dev/null
+++ b/app/src/main/res/menu/help.xml
@@ -0,0 +1,32 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/uart_menu.xml b/app/src/main/res/menu/uart_menu.xml
new file mode 100644
index 000000000..5028e3a08
--- /dev/null
+++ b/app/src/main/res/menu/uart_menu.xml
@@ -0,0 +1,30 @@
+
+
diff --git a/app/src/main/res/menu/uart_menu_config.xml b/app/src/main/res/menu/uart_menu_config.xml
new file mode 100644
index 000000000..f06bda8a5
--- /dev/null
+++ b/app/src/main/res/menu/uart_menu_config.xml
@@ -0,0 +1,30 @@
+
+
diff --git a/app/src/main/res/raw/ble_app_hrs_dfu_s110_v7_1_0.hex b/app/src/main/res/raw/ble_app_hrs_dfu_s110_v7_1_0.hex
new file mode 100644
index 000000000..64ce6f471
--- /dev/null
+++ b/app/src/main/res/raw/ble_app_hrs_dfu_s110_v7_1_0.hex
@@ -0,0 +1,1080 @@
+:020000040001F9
+:10600000F82F002039610100536101005561010042
+:106010000000000000000000000000000000000080
+:1060200000000000000000000000000057610100B7
+:106030000000000000000000596101005B610100E8
+:106040005D6101005D610100456401005D61010069
+:106050005D61010000000000896201005D610100D6
+:106060005D6101005D6101005D6101005D61010034
+:106070005D6101005D6101005D6101005D61010024
+:106080005D610100596301005D6101005D61010016
+:10609000796301005D610100CD6301005D61010074
+:1060A0005D6101005D610100000000000000000072
+:1060B00000000000000000000000000000000000E0
+:1060C0000348854600F0B0F800480047F58F01000E
+:1060D000F82F0020056885F3088846680A4AEFF320
+:1060E00005839A42304602D1084CA6463047074CF9
+:1060F000064D0646064FF0B4034C034D024E024FC8
+:10610000F0B404480047000000000000FFFFFFFF5C
+:1061100000000021F9FFFFFF401E00BF00BF00BFCD
+:1061200000BF00BF00BF00BF00BF00BF00BF00BF77
+:1061300000BFF1D1704700000321094802680A43FB
+:106140000260084802680A4302600748804707481F
+:106150000047FEE7FEE7FEE7FEE7FEE7FEE700009A
+:106160002405004054050040F1630100C1600100B6
+:1061700030B50B46014600202022012409E00D46DF
+:10618000D5409D4205D31D469540491B2546954067
+:1061900040191546521E002DF1DC30BD10B5431AD2
+:1061A000934209D28318881803E0401E01785B1ED1
+:1061B0001970521EF9D210BD03460B439B0703D042
+:1061C00009E008C9121F08C0042AFAD203E00B78BC
+:1061D0000370401C491C521EF9D210BDD2B201E01E
+:1061E0000270401C491EFBD270470022F6E710B532
+:1061F00013460A4604461946FFF7F0FF204610BD35
+:10620000421C0178401C0029FBD1801A704730B530
+:1062100004460020034600E05B1C934203D2E05C8E
+:10622000CD5C401BF8D030BD064C0125064E05E084
+:10623000E36807CC2B430C3C98471034B442F7D3A7
+:10624000FFF742FF1CA301003CA3010030B58C18EE
+:106250000278401C13071B0F01D10378401C120960
+:1062600006D10278401C03E005780D70401C491CE3
+:106270005B1EF9D101E00B70491C521EFBD1A142FB
+:10628000E6D3002030BD0000F8B51F4805691F495E
+:106290000020C863074632E014213A464A43416869
+:1062A000806854180121B940014226D0E068C643F5
+:1062B00020686E4006403146204603F0B3FB12488A
+:1062C000006968400CD00543E0682168C04368401D
+:1062D000084001460090204603F0A4FB00980643C6
+:1062E0003046A168A84301402A4660683240104009
+:1062F00002460A4301D0226990477F1CFFB204483E
+:1063000041788F42C8D3F8BD00050050406100407D
+:10631000182100208307FF22DB0E9A408907090E0F
+:10632000994000280BDA0007000F08388308084856
+:106330009B001818C36993430B43C36170478308DC
+:1063400004489B001B181868904308431860704766
+:1063500000ED00E000E400E010B506490020086010
+:1063600048608860C860034940390860486003F0AD
+:106370002BFE10BD40110140FEB50020C043124D60
+:1063800002906869019068462E6902F0F1FA0746AA
+:1063900002F06EFD0446002F08D002AA0199009871
+:1063A00002F006FB029802F0ADFD06E0029802F052
+:1063B000A9FD002801D1002C02D0304601F06AFC72
+:1063C00000206871FEBD00002421002010B50748A0
+:1063D0004068002807D08047002803D000221146DB
+:1063E00000F038FA10BD02F0BFFC10BD48210020BB
+:1063F00010B502F007FD002805D00E490C48486092
+:10640000C8130D4988610D48018CC9B201290ED10C
+:10641000818C09070BD1018D0906090F042906D1CA
+:10642000808D0006000F02D105490120886010BD53
+:10643000DFFF07C000050040006C0040C00F00F007
+:10644000000600407CB51B4CA0681B4E00250028B0
+:1064500018D0A56019488069C1B2194800F01DFA2A
+:10646000002805D001216A4611700190684607E0B6
+:106470001348C1688068081A012803D1684605706E
+:10648000B1688847E069002803D0E561032002F085
+:1064900039FF606A00280BD06562084980390868B6
+:1064A000086002216A4611700190B1686846884709
+:1064B0007CBD000000210040D820002000250040C5
+:1064C000CC26002030B47446641E2578641CAB4290
+:1064D00000D21D46635D5B00E31830BC184702E044
+:1064E00008C8121F08C1002AFAD17047704700205F
+:1064F00001E001C1121F002AFBD1704738B50D4CD5
+:106500006078022802D00C490120886069460B4857
+:1065100000F0B2F9002802D000F016F838BD0848A3
+:10652000802181606A46074B1278DA61022262702C
+:10653000416038BDD820002000200040DC2600202B
+:1065400000230040002500401CB506490120C8601A
+:106550000549487003206A46107089686846884774
+:106560001CBD000000200040D8200020044900206D
+:10657000487004490122CA604A60034908607047B4
+:10658000D82000200020004000250040F7B582B050
+:1065900000260546167000781446002805D02846C7
+:1065A000039902F0B3FD06002DD1A87800281BD076
+:1065B0002078039F001D1F2824D8684679DF002813
+:1065C00021D121780322481C20707A542178192285
+:1065D000481C20707A542078C1196846008803F05E
+:1065E0007DFD217840182070A988002923D0012042
+:1065F0008446AA680398002A07D0227853189B1C67
+:106600001F2B04D90C2005B0F0BD0720FBE7134673
+:10661000521C2270491CC15422786746511C2170BB
+:1066200087542378AA881818A968FFF7B7FD20783F
+:10663000297940182070E868002815D000214156BB
+:1066400022788C46D01C03991F28DBD8501C0223CB
+:1066500020708B5422780A23501C20708B54207891
+:106660006246431C23700A54288A002809D0284611
+:106670000094062202211030039B03F0ADFD0600BA
+:10668000C1D1288B002809D0284600940722032175
+:106690001830039B03F0A0FD0600B4D1288C2F46D0
+:1066A0002037002808D00094152214213846039B77
+:1066B00003F092FD0600A6D1A86A002805D0224664
+:1066C000039901F039FB06009DD1E86A002805D046
+:1066D0002246039902F0E8FC060094D1387D002898
+:1066E00006D022462846039903F0FAF906008AD11B
+:1066F000304688E700B593B006206946164A087010
+:1067000007CA0FAB07C3382101A8FFF76EFD0220AF
+:1067100069460871012088710881032003918883EC
+:106720000FA80890002101A800F07CFB002803D0EE
+:106730000022114600F08EF814210848FFF755FD9D
+:106740000648002101704160017228210182B421B4
+:10675000418213B000BD0000A0A20100C8240020A7
+:1067600008B5684602F06CFE002803D000221146EE
+:1067700000F070F80098002803D00B490120487001
+:1067800008BD0A4873DF002803D00022114600F03C
+:1067900061F8022000F0FAFF0028F1D00022114633
+:1067A00000F058F808BD000000200020C824002098
+:1067B000044842680121C9050B46934383600A409F
+:1067C000C26070470005005010B5054841680029B7
+:1067D00003D0407800F0BEF810BD082010BD0000C6
+:1067E000FC200020F8B50C46052A01D20720F8BD90
+:1067F0001449154D48600C70CB608A60002101235C
+:106800000BE0E200121816789778B600BF007619F0
+:10681000376016781A46B24011432246641EE4B22D
+:10682000002AEED10748094B0A46401C00F0DCF86C
+:106830000028DCD10348064A0021103000F010F98E
+:10684000F8BD0000FC20002000070050A58B0100CF
+:10685000817E0100BFF34F8F03490248C860BFF338
+:106860004F8FFEE70400FA0500ED00E0014B1B68C6
+:1068700018470000A4260020C26883689A420AD004
+:10688000826883881A4003689A5C0A708168491C90
+:1068900081600020704705207047C2688368D31A62
+:1068A0008288934208D8C36813400268D154C168F3
+:1068B000491CC160002070470420704710B50029B2
+:1068C00003D08A070024002A01D0072010BD0F4AF8
+:1068D0000B46516010701421547041431846946067
+:1068E000FFF783FC00200A49C04388600948402123
+:1068F0000160094A1068FF231B0498430B041843E6
+:10690000106006480160002010BD00001821002022
+:106910000063004080E200E004E400E000E100E009
+:106920000A4B5A68002A04D05B78984203D30720A8
+:10693000704708207047142358438018044A12698E
+:106940000068024000200A60704700001821002003
+:1069500000050050F0B51F4A5168002904D0537853
+:10696000984203D30720F0BD0820F0BD1A4B002544
+:10697000DD631A4B1C699768002F02D1184EDB05A6
+:106980007360012686403E439660142250434218AD
+:10699000D560002201230D5893401D4215D01C42A2
+:1069A00002D003252D0405E001256D044618F76883
+:1069B0001F43F7600B4B9600F3181E6803273F0434
+:1069C000BE431E601E682E431E60521C202AE1D367
+:1069D0000020F0BD1821002040610040000500505B
+:1069E0000063004000070050F8B5114F84467D68F1
+:1069F000002D07D0002B07D078783C78A04205D333
+:106A00000420F8BD0820F8BD0720F8BD0E46142468
+:106A1000164344432E5164192361A26061606146AC
+:106A20000870411C797002F0CDFC0020F8BD000018
+:106A30001821002038B50246084814230178074C75
+:106A400059430818C368CC341B01E4580179C38842
+:106A5000009343690C30A04738BD0000EC260020AD
+:106A600070B5114CA568002D06D0002A06D000286C
+:106A700004D00023247809E0082070BD072070BDF1
+:106A800024265E43AE5D002E04D05B1CA342F7DBE0
+:106A9000042070BD242401265C432E5564196170C6
+:106AA000A2610360002070BD24210020FFB598077B
+:106AB000002481B01E4615460F4600280BD1002E3B
+:106AC00009D002F08DFF22490A9888610F70324682
+:106AD00000208E6008E0072005B0F0BD242343436A
+:106AE000D4549B181C75401CB842F7DB2420474344
+:106AF000BB19032048700F461846CB601946183062
+:106B0000002218232E465E43D3005B181C705C7075
+:106B10009D7058603018521C032AF5DB0020C043DA
+:106B20003861BC70FC7001240A482405046003210C
+:106B30001420FFF7EFFB08480460084C0198A060A0
+:106B400003211120FFF7E6FB606878610020C3E7AE
+:106B50002421002080E200E000E100E00015014077
+:106B6000FEB504461D48174682680D46002A0CD023
+:106B700001788C4201D2052D01D20720FEBD2146AD
+:106B800024235943535C012B01D00820FEBD8818F3
+:106B9000407801281DD0002603F0E2FAC0000F491A
+:106BA0000190C9684018694603F004FB002812D020
+:106BB000012101700A49446049680830E2C0074970
+:106BC0000198C96841180098487003F08BF90020BB
+:106BD000FEBD2E46E0E70420FEBD0000242100207B
+:106BE00000150140F8B5144D0446A868002809D0E6
+:106BF00029788C4201D30720F8BD24216143405CF1
+:106C0000012801D00820F8BD03F0AAFAC600E86800
+:106C10006946301803F0CEFA00280AD0022101702C
+:106C20004460E86831180098487003F05BF9002070
+:106C3000F8BD0420F8BD00002421002010B5184C38
+:106C4000012100220904E068FFF78AFF002803D031
+:106C500000221146FFF7FEFD01210022C903206931
+:106C6000FFF77EFF002803D000221146FFF7F2FD58
+:106C700000220C496069FFF773FF002803D000224F
+:106C80001146FFF7E7FD05210022C903A069FFF7C0
+:106C900067FF002803D000221146FFF7DBFD10BD7F
+:106CA0000020002066260000F0B50546007D002487
+:106CB00093B00E4600280DD00120014668461031E1
+:106CC00003940173717B4173817BF9200140684615
+:106CD000891C81731C2104A8FFF787FA6946087C88
+:106CE00002210843694608742A7DEF21002A02D058
+:106CF0001022104300E008406A461074059407947F
+:106D00000894287D002800D003A8099001270A9440
+:106D100010469771304A82800094B27B0270F27BF9
+:106D200042708078F9221040F722801C1040EF2139
+:106D300008404108490068468170317B0172142186
+:106D40000BA8FFF752FA01A80B9068460C908786B3
+:106D5000C486078702A80F90A888AB1D0BAA04A9B8
+:106D6000A2DF002835D1B068002830D0684687718E
+:106D70001A4981800094317C0170002001466846E8
+:106D800041708078F9210840F721801C0840EF21EC
+:106D90000840410849006846817011A8B16800F0B8
+:106DA000A5FB064614210BA8FFF71FFA01A80B90BC
+:106DB00068460C908686C486068711A80F902A46DE
+:106DC000E8880E320BA9A3DF002802D100E0EC8195
+:106DD000002013B0F0BD0000192A000008290000AF
+:106DE00010B50C490C4800F05DFBC1B20948683889
+:106DF00000F04CF800280BD0082809D00749884239
+:106E000006D00749884203D000221146FFF722FD31
+:106E100010BD0000442500201C200020043000008C
+:106E20000134000070B50D4692B000216A46117021
+:106E300007241171002812D08188002905D08168AB
+:106E4000002902D00978490702D4204612B070BD4B
+:106E500002A9FFF79BFB0028F8D102AE00E0002654
+:106E6000002D0BD0A8880028EFD101AA0AA9284636
+:106E7000FFF78CFB0028E9D10AAA00E0002268464F
+:106E800003790178304672DFE0E7000033B585B062
+:106E900004466946227C097E00208A4222D00125D0
+:106EA000684605822174E08806AB04AA0021A4DFAD
+:106EB000002817D1608A0D49884215D0207D00280E
+:106EC00012D00020009001900290694603900D823C
+:106ED000E2880A808D70888004A8029006A803903A
+:106EE000608AA6DF07B030BD0820FBE7FFFF000087
+:106EF00038B50446086820600020C04360820879E5
+:106F00002075FF200D4620740121684681700649D6
+:106F10000180221D69460120A0DF002803D12946F7
+:106F20002046FFF7C1FE38BD0F18000038B50A88AB
+:106F3000102A1AD0112A1AD0502A1BD1027D002AF9
+:106F400018D0CA884389891D9A4213D10A8B022A14
+:106F500010D10268002A0DD0CB7E8C7E190221430D
+:106F6000C907C90F07D0002106E0898801E0002188
+:106F7000C943418238BD01216B461970694690476B
+:106F800038BD000070B5054601461C220F48FFF7CA
+:106F900005F90F4C002626702968002907D0082221
+:106FA000A018FFF7FBF8204608307ADF02E0084817
+:106FB00008307BDF002808D1401E608004486670DE
+:106FC000044A0021001DFFF74BFD70BDB0260020D4
+:106FD000C0200020FDA00100F8B5224C0288002747
+:106FE0001F4DE689102A16D029464968112A21D05A
+:106FF000122A2DD0502A1CD1C288801DB24218D12D
+:10700000028B022A15D1C27E837E10021843C0076C
+:10701000C00F0CD112E08188698001461048082217
+:107020000E311030FFF7BAF86F70002E01D100F06A
+:10703000CBFEF8BD0020C04368806F700846FFF7A4
+:10704000D1FD0028F5D0A1690029F2D08847F8BD0C
+:10705000811D034808221030FFF7A0F8E7E7000081
+:10706000C0200020B026002010B502484068FFF77D
+:10707000B9FD10BDC020002030B585B00D4604001C
+:1070800039D0002D37D06868002834D00020C043A4
+:107090001A4B20800FCB049301AB07C318486946F5
+:1070A000088001A8811E63DF002822D1221D6946C5
+:1070B0000120A0DF00281CD168468078A0712046FE
+:1070C00001F0B2F8002814D1204601F069F8002838
+:1070D0000FD12946204601F0E9F8002809D1686857
+:1070E000A062A868002800D0E0620649012008706C
+:1070F000002005B030BD0E20FBE70000DCA201003F
+:1071000030150000542000203EB5002820D0002972
+:107110001ED0826A002A1BD00A88102A19D0112A90
+:1071200028D0502A17D0512A12D104468879891DB7
+:1071300002280DD14888228A904209D12046891C14
+:1071400002F072F8002803D0E16A002900D08847D5
+:107150003EBD898810E0CA8803899A42F8D1082286
+:107160006B461A708A7F1A7120310291826A6946D1
+:1071700090473EBD0021C94301803EBD30B585B07A
+:10718000002822D00388134CA34220D0124B1B7836
+:10719000002B1CD0104B10255B1C1D705970002457
+:1071A00001259A70032269460A8200940194029490
+:1071B0000394028A0A808D708C8004A90291039343
+:1071C00000886946A6DF05B030BD0E20FBE7082029
+:1071D000F9E70000FFFF0000542000207FB50446BF
+:1071E0000121684681714A4981804A4A01A90120EA
+:1071F000A0DF00287ED1228825463C35002A09D010
+:1072000044482B46801C00904348616800F034FCE1
+:1072100000287BD12289002A0AD03E482B460A301A
+:1072200000903D48E168401F00F026FC00286DD129
+:10723000228A002A0AD037482B461230009036485E
+:107240006169001F00F018FC00285FD1228B002A22
+:107250000AD030482B461A3000902F48E169801E32
+:1072600000F00AFC002851D1228C002A0AD02948BB
+:107270002B46223000902848616AC01E00F0FCFBBB
+:10728000002843D1228D002A0AD022482B462A30DA
+:1072900000902148E16A401E00F0EEFB002835D145
+:1072A000216B00290ED002A802F0F0FD19482B46F0
+:1072B000323000901848082202A9801F00F0DCFB41
+:1072C000002823D1606B00280BD012492B463A319D
+:1072D0000091027901681048401C00F0CDFB0028A5
+:1072E00014D1A16B002910D002A802F089F8094836
+:1072F000423000E00AE0009007482B46072202A92E
+:10730000273000F0B9FB002800D1002004B070BD88
+:107310000A1800005C200020292A000010B504464D
+:1073200001F00CF821460A4800F06EF808482146A2
+:107330001838FFF7FBFD2046FFF74EFE04482146B4
+:107340008030FFF7E1FE204601F01CFF10BD000079
+:10735000F424002030B50446008C134A8BB09042D0
+:107360001FD006AA204601F0BBFC694605460880EE
+:107370000020019002900390049022898A800122CB
+:107380008A71088106A803910490208C01A9A6DFC8
+:10739000002804D169460988A94200D00C200BB00E
+:1073A00030BD0820FBE70000FFFF000038B50446B1
+:1073B00008682060087960710020C0430D46208471
+:1073C0000020222108554C21085301216846817074
+:1073D0000C490180A21D69460120A0DF00280FD1C1
+:1073E0002946204601F024FC002809D1A86800287D
+:1073F00005D02946204600F0B1F8002800D1002031
+:1074000038BD00000D18000038B50A88102A17D0C2
+:10741000112A17D0502A18D1CA888389891D9A4207
+:1074200013D10A8B022A10D10268002A0DD0CB7E1C
+:107430008C7E19022143C907C90F07D0002106E03D
+:10744000898801E00021C943018438BD01216B46D0
+:1074500019706946904738BD70B5054604464035F9
+:10746000A8890E46142807D12622A118881EFEF7E7
+:1074700095FEA889401EA88180B241000919401CD0
+:10748000A8818E8470BD2030817070470A7B002AED
+:1074900003D049680160002102E009680160012110
+:1074A0000171704710B504798B680268002C08D010
+:1074B00049688C1A9C4201D9D11807E0016000216B
+:1074C00008E00968541A9C4202D9D11A016002E00E
+:1074D000016001210171006810BD70B504460D46C0
+:1074E0000846FEF78DFE2080656070BD0A78027048
+:1074F000497841700220704708B500235022144992
+:10750000082002F0D1FB002803D000221146FFF72B
+:10751000A1F90020694600900870684660DF0028E5
+:1075200003D000221146FFF795F90A4802F0B2FB9A
+:10753000002803D000221146FFF78CF9064802F01C
+:10754000CBFB002803D000221146FFF783F908BDCA
+:10755000642400201D730100659E0100F0B58FB00A
+:107560000D4606461C216846FEF73FFE6846017838
+:10757000022001436846017000240194039404949E
+:107580000594012708A80694877015496846018468
+:107590000794E97B0177297C4177807FF9210840B6
+:1075A000F721801C0840EF21084041084900684647
+:1075B0008177142109A8FEF718FE08A8099007A8EA
+:1075C0000A9068468785C4850786A8680D9033466B
+:1075D000F088103309AA6946A2DF0FB0F0BD0000A1
+:1075E000382A00001CB5044681786A460C4801F030
+:1075F0003BFF002812D160781821484309491C2319
+:1076000040181822694601F0E3FF064806493C305D
+:10761000807A80000A582146684690471CBD0000C9
+:10762000B820002068260020FCA2010070B51C4D87
+:1076300068688047B1203CDF002803D00022114653
+:10764000FFF708F911DF002803D000221146FFF7E9
+:1076500001F9144C606913DF002803D000221146A1
+:10766000FFF7F8F82878002801D000F06BFD012121
+:107670000D4A880510600D4A002012680B4B8033BC
+:107680000D468540154204D0C606F60E0D46B5409F
+:107690001D60401C2028F3D3606900F009F870BD1C
+:1076A000F02000200010001080E200E000E100E087
+:1076B00010B5FEF70FFD10BD30B4002201290BD12B
+:1076C0000B49C968002907D00A4B94001C59844211
+:1076D00004D0521C042AF8D330BC7047042AFBD2D1
+:1076E00003481030805C0028F6D030BC084700000A
+:1076F00034200020ACA2010010B5024604280DD2AF
+:1077000007480069074C9200A45CC3430122002093
+:10771000A2401A4000D001220A7010BD072010BDFF
+:1077200000050050ACA20100F0B500210F4D01276B
+:107730000F4A03231B048C002E593C468C40044204
+:1077400007D0B400A41826689E43266026681E430E
+:1077500005E0B400A41826689E43266026682660CB
+:10776000491C0429E7D30020F0BD0000ACA20100B1
+:107770000007005002460020042A02D2024B99540E
+:1077800070470720704700004420002010B5054ACC
+:107790000146127A0020D20702D0084600F054F8C1
+:1077A00010BD000034200020F8B506462148002412
+:1077B0008660C26041600F46B0071DD5002528468F
+:1077C0000B30C1B22846FFF7D5FF6D1C0446042DCF
+:1077D00001D2002CF3D07A08002304211648FFF7C9
+:1077E00001F8002803D000221146FFF733F8002CDF
+:1077F00002D1FEF7E9FF0446F00708D00F210F4839
+:107800004904C161090181600022C2608161002CCC
+:107810000ED108480A4A00211430FFF721F904006C
+:1078200006D10448074A01211830FFF719F9044628
+:107830002046F8BD34200020BCA201000005005005
+:10784000598E0100B1670100FEB5784906464F68C0
+:107850004B690093BB00FB180020012201935205E5
+:107860000546734C3300FEF72DFE180D13133749F0
+:10787000596C828E858E97A1A5A5A5A5A5C3C8CD57
+:10788000D4DADA120B225205A260E5600D70FEBD5B
+:1078900005208005A060E56060680F235B05584007
+:1078A000800209D5A260E560022E02D1E120C0006D
+:1078B0000AE07D20400107E0E260A560022E01D1D0
+:1078C000C82001E0FF2091300E706421FEF750FCCB
+:1078D00078430022014646E005208005A060E5606F
+:1078E00060680F235B055840800204D5A260E56004
+:1078F00019204001E8E7E260A560E1E70520800586
+:10790000A060E56060680F235B055840800202D5E7
+:10791000A260E560CDE7E260A560D3E705208005C1
+:10792000A060E56060680F235B055840800202D5C7
+:10793000A260E56001E0E260A5600E7079000022BF
+:1079400011E005208005A060E56060680F235B05FD
+:107950005840800202D5A260E56001E0E260A560C7
+:107960000E70002239460098FFF7FAF8FEBD052395
+:107970009B0544E06068012189050A468243A260B4
+:107980000840E06010E061680120800502468A43FB
+:10799000A2600140E160E4E76168012080050246E1
+:1079A0008A43A2600140E1600199C8E70F214905BF
+:1079B000E1601BE00F468869FFF714F911218A1B6B
+:1079C0001C4E01D0002802D0A660E560FEBD012A51
+:1079D0000BD91046C82148436421FEF7C9FB7968DA
+:1079E00000224143B869FFF7BBF8E660A560FEBD21
+:1079F0000F225205A260E56015E007239B05A360F6
+:107A0000E5600EE00D225205A260E56001229205BC
+:107A100007E00322D205A260E560921001E00F2288
+:107A20005205E260A5600E70FEBD0000342000200B
+:107A3000000500500000800010B50E4AD368002BEE
+:107A40000AD0CAB2017840689847002803D00022C3
+:107A50001146FEF7FFFE10BD012906D00029FAD11C
+:107A6000037842681846904710BD037801214268A8
+:107A7000F8E70000FC200020FFB58FB01E46154639
+:107A80000F0004D1002211461046FEF7E3FE002D40
+:107A900004D1002211461046FEF7DCFE1C21684688
+:107AA000FEF7A3FB68460178022001436846017097
+:107AB00000240194039404940594012108A80694D9
+:107AC000817068460F990184079431780177717845
+:107AD0004177807FF9210840F721801C0840EF2181
+:107AE00008404108490068468177142109A8FEF73B
+:107AF0007CFB08A8099007A80A9068468585C4857C
+:107B0000058604480D9709AA69460088189BA2DFDC
+:107B100013B0F0BD5C200020F8B5384914230A7872
+:107B20000F205A4354183649227926698F88241D1C
+:107B3000022A1BD001252D07042A2AD0052A5BD151
+:107B4000286981B23046FEF713FB0146A3882869F5
+:107B50009A1980B24843101A82086088181886194A
+:107B600028694B1C80B25843801B34E0B8026288FD
+:107B70002169121A0918A3883018181801239B02CA
+:107B80009A4202D2920821DF31E0FF22013221DF46
+:107B90002DE0A36819481901CC30081801894289E1
+:107BA0004068B04203D160885143814218D0022B13
+:107BB00016D0286981B23046FEF7DAFA0146286904
+:107BC00080B24843301A820828694B1C80B258435F
+:107BD00063889B19C01A8308204602F089F806E0E2
+:107BE000286981B23046FEF7C3FAC01920DF0028A9
+:107BF00002D1024901228A70F8BD0000EC26002063
+:107C0000E420002010B5064900204978002906D05C
+:107C1000FFF782FF002802D0112800D1002010BDFC
+:107C2000EC26002014225043054A002180180171DF
+:107C3000C18001220161C260416101817047000081
+:107C4000EC260020F8B5124C069E65780A2D1DD052
+:107C500027787D19EDB20A2D01D30A3DEDB2142724
+:107C60007D432D1928716A6103C92961E860EB80A1
+:107C70002E81A1780020002904D1FFF74DFF1128A3
+:107C800000D100206178491C6170F8BD0420F8BD66
+:107C9000EC260020F8B5224801694A1C3DD0242278
+:107CA0005143826889184E681E494C6847690079BB
+:107CB000E11B0D022D0AED1C002814D101201A4AE7
+:107CC00000045060184A403A50601849400008606B
+:107CD000174908601749012008602F20FEF71CFA99
+:107CE0000F4901200871B54200D23546E81900025B
+:107CF0000C49000A403108600A494968001B091B09
+:107D00000902090A0002000AC91C814204D90120A3
+:107D10000649400480390860F8BD01F061FEF8BDF5
+:107D200024210020001501404013014080E200E0C2
+:107D300000E100E000100140F8B50D46117806465C
+:107D4000881D14461F2801D90C20F8BD3388072050
+:107D5000062BFAD31927FF01BB4202D9164A9342D8
+:107D6000F3D17288062AF0D3BA4202D9124FBA422E
+:107D7000EBD1114FBB4203D0BA4201D09342E4D8B9
+:107D8000481C052220706A5420781222411C217060
+:107D90002A5420784119308802F0A0F9217840183F
+:107DA000C0B220704119708802F098F9217840180B
+:107DB00020700020F8BD0000FFFF000010B5002279
+:107DC0001146FEF747FD10BDF8B5164801F00CF856
+:107DD000144D0026103D144C002807D0616900297D
+:107DE0001BD001206A461070684615E028780028EC
+:107DF00005D06169002910D0684606700CE06878EB
+:107E000000280CD0A16800226868FEF7A9FE0028AF
+:107E100003D0A169002900D088472E70F8BD6168A1
+:107E2000F1E70000D0200020B026002070B5094BFB
+:107E300004469E7A082000250E4207D00648844258
+:107E400002D01889A04202D10020156070BD052023
+:107E500070BD000098260020FFFF00000C21484361
+:107E6000064910B5012240188272911E0181FF213E
+:107E7000C1720721FEF7B9F910BD00009826002055
+:107E8000F8B5184D69466878FEF74AFD00281DD1FF
+:107E90002F4600241437012615E06968E0004018D9
+:107EA000037831463A6899400A4210D04268002A65
+:107EB0000DD04178012901D1002100E00121FFF717
+:107EC000BBFD641CE4B228788442E6D3F8BD7A682E
+:107ED0000A42F6D041680029F3D041780129EDD05B
+:107EE000EAE70000FC200020FEB50E46044681783B
+:107EF0006A46234801F0B8FA002827D160780C219F
+:107F0000204F4843C019807A800620D4022E1ED00C
+:107F1000012E1ED01C4DA0781C2148431949002376
+:107F2000F43941181C226846A847002813D0022EB5
+:107F30000CD020780C21484312490C314018807A2B
+:107F4000124980000A58214668469047FEBD104DF0
+:107F5000E1E7104DDFE7022EF8D060780C214843AE
+:107F6000C019817ADF22114081726078182148435C
+:107F700004491C233039411818226846A847D8E71D
+:107F8000B820002098260020ED9C0100ECA2010002
+:107F9000D99801007197010070B50546114A124841
+:107FA00000241C2363431B189B7EFF2B04D0641CFE
+:107FB000072CF6D3104670BD0A78022A09D01C227D
+:107FC00062431018FD22827607221030FEF7E6F891
+:107FD00004E01C2161430818FE2181762C700022E8
+:107FE000E8E7000043800000A4250020F7B5052540
+:107FF0000F4F06460024002E0AD01C206043C019F3
+:10800000072231461030FEF702F9002807D00AE0B7
+:108010001C206043C019018B0298814203D1019852
+:108020000025046002E0641C072CE4D32846FEBD52
+:10803000A425002070B505460C46100003D0002290
+:108040001146FEF707FC2078122805D0142801D02D
+:10805000212804D1284600E0002000F061F800202B
+:1080600070BD00001FB501F067FA002803D00022A0
+:108070001146FEF7EFFB03A90120FFF73DFB0028A7
+:1080800003D000221146FEF7E5FB03A800F03CFBFD
+:10809000002803D000221146FEF7DCFB01A80621D0
+:1080A0000230FEF7A2F81E206946C880087A012235
+:1080B0001043E1210840DF210C3008406946087276
+:1080C0000720487210208872064800900A710648FE
+:1080D00000F022FC002803D000221146FEF7BAFB74
+:1080E0001FBD0000358001000320002010B5097875
+:1080F000002902D8FFF79AFA10BD03220121FFF7E9
+:108100003DF80028F8D000221146FEF7A3FB10BD71
+:10811000704700000149486070470000F0200020CF
+:1081200010B50021064C0028217007D001462046DA
+:1081300004220830FEF732F80120207010BD000044
+:10814000F020002000B587B06946134800F08EFA91
+:10815000002803D000221146FEF77CFB0499072279
+:108160000E481031FEF71AF80C48142224380399EF
+:10817000FEF714F80948102210380499FEF70EF89B
+:108180000648243800DF002803D000221146FEF7FD
+:1081900061FB07B000BD0000F8200020EC270020A4
+:1081A00030B58FB005461C216846FEF71EF86946BB
+:1081B00008780821084310221043694608700024FB
+:1081C00001940394049405940694A87908A98870EE
+:1081D000144869460884601C00070794000F0C7758
+:1081E00010304877887FF9210840F721801C08402B
+:1081F00010430121084369468877142109A8FDF737
+:10820000F4FF08A8099007A80A9069468C851720F2
+:10821000CC8508860D942B46A888103309AAA2DFC6
+:108220000FB030BD3115000030B58FB005461C21B0
+:108230006846FDF7DAFF6946087804210843694675
+:108240000870002401940394049405940694A8797A
+:1082500008A98870144869460884601C00070794C0
+:10826000000F0C77103048778A7FF9200240F72002
+:10827000921C0240EF200240012002438A77142121
+:1082800009A8FDF7B2FF08A8099007A80A90694657
+:108290008C851420CC8508860D942B46A88808333D
+:1082A00009AAA2DF0FB030BD32150000F0B58FB0C3
+:1082B0000F4605461C216846FDF797FF6846017882
+:1082C0000226314301700024019403940494059420
+:1082D0000694A97908A88170174968460184601C32
+:1082E0000107090F68461031079401770020014605
+:1082F00068464177817FF9200140F720891C0140C1
+:10830000EF2001400120014368468177142109A82C
+:10831000FDF76BFF08A8099007A80A9068468685B4
+:10832000C48506860D972B46A888203309AA69467E
+:10833000A2DF0FB0F0BD000034150000F0B5054617
+:1083400087B0FF24C3480594007800286BD0C24F43
+:108350003868002867D001A800F0C8F9002803D0C9
+:1083600000221146FEF776FA002006460690281DE8
+:108370000390342168460182FF218171002101713F
+:108380000091B54C28880C3C10280ED0A8886A466D
+:108390000221FFF74BFD002807D100980C21414333
+:1083A0000919CA7A69468A7148712888142876D0D2
+:1083B0002DDC102839D0112873D0132833D1694609
+:1083C00008723A1DA88800217FDF00282BD1009871
+:1083D0000C21414309198A7A04231A438A72C97A03
+:1083E000FF2913D016216A461172182148439A4971
+:1083F000243940181421001DFDF7F7FE00980C21C8
+:1084000048430019817A2022114381720FE1172815
+:108410007ED0924918287CD0522804D1B87A8000A6
+:10842000095801A8884707B0F0BD8D4F6A46012161
+:108430003846FFF7FBFC0028F5D100990C20484393
+:1084400002220019827211236A4613725171A9889F
+:1084500001810722A91DFDF7A1FE687BC10707D096
+:1084600041087C480830405CFF2808D0059006E0B1
+:108470003A4605A9A81DFFF7B9FD0028C6D1009905
+:108480000C22514309190598C8728A7A08231A43A5
+:108490008A726946887101A8FFF7A4F8C7E024E052
+:1084A000FFE700980C2148430119887AFD2210400B
+:1084B0008872010706D540060DD5002101A8FFF7F7
+:1084C00013FD08E068468079FF2804D000F05EFFC5
+:1084D000FF206946887100990C2251431020091928
+:1084E0008872122069460872A1E000980C21484366
+:1084F0000019C07AFF281FD1EA8905A90020FFF7DB
+:1085000075FD002811D100990C225143091901E091
+:108510001FE079E00598C8728A7A08231A438A72A4
+:108520006946887101A8FFF75DF800980C2148435F
+:108530000019C07AFF2807D00098182148434649FF
+:108540003C394018001D0690A8880022069981DF5A
+:1085500069E700980C21414309198A7AFB231A40E4
+:108560008A7214216A461172A979002901D00E4637
+:108570005DE018214843384914223C394018E97A13
+:108580008170A97A417029460C31001DFDF706FE65
+:1085900000980C2148430119887A420647D50822E1
+:1085A00010438872CA7AFF2A04D0202210438872AE
+:1085B000002125E005A8FFF7EFFC002801D00646C2
+:1085C00035E069460598887100990C2251430919D4
+:1085D000C8721C22234F5043A989C0190183E97A2C
+:1085E00049070CD5294610222031FDF7D7FD059803
+:1085F0001C214843C019817E490849008176012128
+:1086000001A8FFF771FC12E000980C2250430019FA
+:10861000827A40231A43827215206A461072B87A11
+:108620008000095801A88847002800D00E4E02A9F2
+:10863000324601A8FEF71AF96846007A122887D157
+:1086400000980C2141430919897A012980D0FFF74C
+:1086500005FCE8E6A8200020A42600200CA30100C9
+:10866000FFFF0000A42500204180000030B51B4A18
+:108670001B4B1278002A2CD09A1D002805D0002907
+:1086800003D00278002A24D101E0104630BD154CF9
+:1086900000222468002C1CD08378072B1BD21C24BA
+:1086A0006343114C1B199B7EFF2B14D04378182574
+:1086B0006B432546C4355B191B1DCB6080784A612E
+:1086C0001C234A6058438A6000190A600861002030
+:1086D00030BD184630BD0248083030BDA82000200B
+:1086E00008800000A4260020A4250020002806D031
+:1086F000FF21017041708170C170002070470148F6
+:10870000704700000E800000F0B585B0040035D041
+:108710000025284928460D608D72FFF79FFB2548EC
+:1087200018212438FDF761FD0026FF27304600F0B0
+:108730002DFE21488755761C072EF7D35021684619
+:1087400081800721C1801D481B490090083168467F
+:1087500000F090FF00280FD1174E0120083E307026
+:10876000207800280CD069468888C988484381B29F
+:108770003046103000F0A2FE05B0F0BD1048FBE717
+:10878000104F00240C48A1B202AA083000F06CFE81
+:10879000002809D11C206043C01900231C2202A913
+:1087A00000F016FF002801D03570E5E7641C072CA7
+:1087B000E8D3E1E7A4260020B0200020C98701000B
+:1087C0000E800000A4250020FFB550488BB0006843
+:1087D000149F1E46002869D0502F67D803A8FFF7C2
+:1087E00085FF002803D000221146FEF733F800254C
+:1087F0006846057305702C4604970596A1B208AA31
+:10880000434800F031FE002806D1082208A90B9841
+:10881000FDF7FDFC002817D0641C072CEED368463A
+:10882000847BFF2C42D00C98042810D039481C219E
+:10883000301AFDF79DFC0127072824D204A80190D7
+:108840000D98002811D017E068468473E7E7502F91
+:1088500009D040206946087068460178032001432A
+:108860006846017047E02020F4E738462849A040D8
+:1088700010394968084219D0204601F035FC202003
+:10888000694608702EE0214818213C38301AFDF75F
+:108890006FFC00280CD01D4C1821243C301BFDF728
+:1088A00067FC002810D04020694608700FB0F0BD6A
+:1088B00004A801902020694608701C2044431548F4
+:1088C0004D73201805900DE0302168460170457306
+:1088D000C7730E480C38807A800703D420461821CD
+:1088E000FDF783FC0C986946032808780BD0022119
+:1088F00008436946087004A80190694603A80D9AC8
+:10890000FDF7B4FFD2E73843F4E70000A4260020C7
+:10891000B8200020A425002070B5064611480D4659
+:10892000007800280ED01049002E0ED0002D0CD05B
+:108930002868002809D00D4C2168002907D00A4872
+:108940000A3870BD0848801F70BD084670BD2060A1
+:108950000622A91D201DFDF721FC2879A072002008
+:10896000307070BDA82000200E800000A4260020DA
+:1089700010B50E4900238A78CC78A24212D0521C3E
+:10898000D2B28A70022A00D18B708A78074B92008B
+:108990001C339A580260486910180002000A4861A6
+:1089A000012010BD0360002010BD00002421002024
+:1089B000F7B5144C0025A76823691EE0242159430C
+:1089C000C9194E68864202D9301A486017E0801BE8
+:1089D000751900264E600E750E699C460B6AB646E8
+:1089E000002E0AD0019E76193602360A8E6076462F
+:1089F000CE6016680E6261461160591CDED123619B
+:108A0000FEBD0000242100200EB511206946087229
+:108A10000A2212A102A87CDF002803D000221146FE
+:108A2000FDF718FF104878DF002803D00022114618
+:108A3000FDF710FF0021FF20913000916A4601915F
+:108A40001080430053809180D08068467ADF0028F0
+:108A500003D000221146FDF7FDFE0EBD4E6F72647D
+:108A600069635F48524D00004103000000207047D9
+:108A70000020704700207047F8B540780D4C18234F
+:108A8000434326460C270021E558183678438019C1
+:108A90000A46002D07D0867A770604D5B60602D49A
+:108AA0001919AAB2091D0089A9DF002800D00248BF
+:108AB000F8BD0000802600204180000070B50D4602
+:108AC000014668781822084C50430019342300F0FE
+:108AD0007FFD002806D16978182251436258521C44
+:108AE00000D1625070BD000080260020F0B587B034
+:108AF0000D46074614206946888268780C21484351
+:108B00001F4E05AA801900896946AADF002833D1C3
+:108B1000687818214843741A001969468A8A001D2A
+:108B2000FDF775FB0028687806D0182148432158C6
+:108B300000290DD0134E0CE00C2141438919897A8C
+:108B4000890719D4182148430019FDF74EFB13E09B
+:108B50000D4E69468A8A22506878182148430019C8
+:108B60006946001DFDF71AFB687818214843011972
+:108B7000342318223846B047002007B0F0BD00006B
+:108B800098260020D99801007197010010B5FFF7D1
+:108B900073FF10BD10B5FFF791FF10BD10B5FFF7C3
+:108BA000A5FF10BD70B5094D04460E462869FEF7B5
+:108BB00019F8002809D1284614300660446034436F
+:108BC0002246A9682869FDF7CBFF70BDFC20002074
+:108BD00010B513491348FEF765FC124C81B2243CD2
+:108BE000A068401CA0600E486038FEF7B3FB002868
+:108BF0000BD0082809D00C49884206D00B4988427E
+:108C000003D000221146FDF725FE0321A068FDF7E1
+:108C1000AFFA002901D0012000E00020207010BD33
+:108C200054250020242000200430000001340000DE
+:108C3000F0B5002406460D46601C010795B0090FEB
+:108C400008A810310C940174297B4174817CF927A8
+:108C50003940891C81741C2105A8FDF7C6FA6846B5
+:108C6000017D10200143684601750CA806940A9006
+:108C70000894099401220B9408A882761749684643
+:108C800001870D94697B08A80175A97B4175817DD9
+:108C9000F7203940891C0140EF200140114308A80A
+:108CA000817514216846FDF7A0FA0EA800900DA862
+:108CB00001900FAA0021304600F012F801466846E4
+:108CC00001811421448181810FA804903346F088EA
+:108CD00008336A4605A9A2DF15B0F0BD372A0000A7
+:108CE000F7B542790025002A00D004252222135C22
+:108CF0000222002B00D01543CBB2FF2907D9012255
+:108D00001543029A090A53709170032602E00299F2
+:108D100002264B7004464034A189002901D010215D
+:108D20000D43002716E0B11C142909D9A189C91BDC
+:108D30004A007900091824302431FDF72FFA0CE09D
+:108D4000029B7A0012189B19928C1A70120A5A70A0
+:108D5000CEB27F1CA189B942E5DCA089C01BA081ED
+:108D6000029805703046FEBDF8B51E4C20780028EC
+:108D700037D02069002807D00026E068002805D0F9
+:108D80000025002E04D013E00126F6E70125F8E7C0
+:108D9000684651DF052806D0002806D0002211467B
+:108DA000FDF758FD04E0012602E02169009888479C
+:108DB000002D12D1608869460880A06861DF05280F
+:108DC00006D0002806D000221146FDF743FD04E03E
+:108DD000012502E0E168A0688847002ED8D0002D68
+:108DE000CFD0F8BD48210020418805480288914233
+:108DF00004D34088814201D8012070470020704789
+:108E0000C82000201048018CC9B2012917D1818CDB
+:108E1000090714D1018D09060A0F03D1828D1206AC
+:108E2000120F0ED0090F012903D1828D1206120FE5
+:108E300007D0032903D1808D0006000F01D0002048
+:108E40007047012070470000C00F00F0800701D07C
+:108E5000002070470120704710B50448017AC90707
+:108E600002D00078FEF7F0FC10BD00003420002096
+:108E7000F8B5234801690091457833E02048E900BE
+:108E8000C0680E1834782AE0182060437168641CAA
+:108E90000818B178E4B2A14200D100240178022977
+:108EA00002D003291BD113E0154A4068242192689F
+:108EB00041438F18397D002911D001F04DF8002071
+:108EC00038750CE024277843C0180275006A03E067
+:108ED0000B4900228B680869471CF3D10861707840
+:108EE000A042D1D128466D1EEDB20028C6D104485B
+:108EF00001690098814201D00120F8BD0020F8BD31
+:108F000024210020F8B50446384800270169009163
+:108F100046785CE03548F100C2688D1851E0601C6D
+:108F200007D0324A24209268604321468018046AA0
+:108F300024E02878182141436A68401CC0B25218C6
+:108F40002870A978884200D12F70284B51682420BE
+:108F50009B684843C0181378012B34D1037D002B44
+:108F600031D193688360D368C36013690361526928
+:108F7000C2611E4A5279002A00D087601B4B82686A
+:108F80005B6996469C46D31A1A02194B120A9A42FA
+:108F900002D2C368D21808E0724663469A1A1202D7
+:108FA000C368120A934202D99A1A426000E04760ED
+:108FB00087600122C7600275921E0262084600F0B7
+:108FC00099FF601CABD1287869788842A7D13046D8
+:108FD000761EF6B200289DD10448016900988142AE
+:108FE00001D00120F8BD0020F8BD000024210020A0
+:108FF000FFFF7F0088B0FEF77FFA01F025F82649D1
+:109000000120FDF75BFC002803D000221146FDF78C
+:1090100021FC002221490320FEF7C6FB002803D0D3
+:1090200000221146FDF716FCFFF71CF8FFF7ECFCD9
+:10903000FDF760FB00F092FD00F024FD1C21684666
+:10904000FDF7D3F80521C90301910F210904002080
+:1090500002910090032269460A730F4940318A89C0
+:109060006946CA8108740E4805900E480690684605
+:10907000FDF788FF002803D000221146FDF7EAFB28
+:10908000FDF7DCFDFDF76CFB40DF0028FCD0002283
+:109090001146FDF7DFFBF7E7B4240020CD0C0000FC
+:1090A00005920100BD7D010070B5054600790E46B0
+:1090B000801C1446C0B21178821C8A181F2A01D95C
+:1090C0000C2070BD0A46491C2170401CB054207809
+:1090D000FF22411C2170325420788119288800F029
+:1090E000FDFF21784018C0B22070AA88002A09D05C
+:1090F000A968002908D08019FDF750F8207829794F
+:1091000040182070002070BD072070BDF8B514789D
+:109110000746A01C15460E461F2803D87878801CE9
+:109120001F2801D90C20F8BD1D20001B80B2694604
+:10913000864608803019801C7DDF0028F3D13878FE
+:10914000022805D168460088704501D8092107E04A
+:1091500078780821002801D0704501D96846008838
+:10916000421C3255641CE2B2B1542978801C0818A4
+:1091700028700020F8BD00207047002070470020B4
+:109180007047000010B50446008810281FD0112831
+:109190002AD019281AD1A079002817D1FEF7F6FA9B
+:1091A000002803D000221146FDF754FB0320FEF7F0
+:1091B000BBFA002803D000221146FDF74BFB34DF39
+:1091C000002803D000221146FDF744FB10BD072004
+:1091D000FEF7DCFA002803D000221146FDF73AFB27
+:1091E0000749A088888010BD0020FEF7CFFA00282C
+:1091F00003D000221146FDF72DFBFDF7B1FA10BD9B
+:109200000020002010B50078002809D105483B2136
+:10921000808876DF002803D000221146FDF71AFB74
+:1092200010BD000000200020F0B589B004460227E0
+:10923000684607700D468783A08A08AB07AA0021FD
+:10924000A5DF0026002804D0E16A00291AD088474B
+:1092500018E06846008CC007C00F13D068468680AF
+:1092600020886946A8DF002812D1A97E28461B3035
+:1092700001220B00FDF726F909430E1C2225272A9F
+:10928000412C4300FF20FE30694688802088A8DFFB
+:1092900009B0F0BD6B461E740CE006221DE0694665
+:1092A0000A750690A26A04A9204690470020EFE7BD
+:1092B0006B461A742B8B022BF1D2EEE768460774CB
+:1092C000F0E7032000E00420694612E00520FBE7F8
+:1092D000298B032905D2032208212046FDF74EFFE2
+:1092E000D6E741780278080210436946888202D0A6
+:1092F00006200874D6E70720FBE70920E4E70322ED
+:10930000EBE7000010B51D4A1D490300FDF7DAF830
+:1093100005040B3120271200487801281FD00228AD
+:1093200008D1032005E04878002804D0032801D1A3
+:109330000220487010BD134B04201860124B01200E
+:109340001860D36892689342F3D0FDF7D7F810BD48
+:109350004878012806D0022804D00328F7D1FDF769
+:1093600005F910BDD06891688842EED1FDF7ECF8A0
+:1093700010BD48780128E8D010BD0000DC26002090
+:10938000D8200020002500400020004070B5054690
+:109390001C200B4968434418204611300621FCF775
+:1093A00024FF20461021FCF720FFFF21217400201C
+:1093B000A176C0432083284600F096FE70BD0000D1
+:1093C000A4250020F0B50C4A002153681422504314
+:1093D00001240A4D03273F041E5822468A401642A4
+:1093E00006D08A0052191668BE43166016681660C9
+:1093F000491C2029F0D3F0BD18210020000700509F
+:1094000070B50D00044604D1002211461046FDF748
+:1094100021FA002C04D1002211461046FDF71AFA59
+:109420002878207069886170090AA170A988E170A4
+:10943000090A2171E8886071000AA07170BD0000FE
+:1094400007490978002906D0002806D0054949783F
+:10945000016000207047082070470E207047000010
+:10946000E4200020EC26002030B5124B1B78002BA6
+:1094700007D0002807D0002A05D00468002C12D19C
+:1094800003E0082030BD0E2030BD0B4B1D68002DC1
+:1094900009D045681889414369185D894543586872
+:1094A0002818884201D8072030BD14600020516080
+:1094B00030BD0000E4200020B8270020F8B50546A4
+:1094C0001B480F460078002805D0002D05D02868DD
+:1094D000002820D103E00820F8BD0E20F8BD154C6F
+:1094E00000262068002816D06089218948436168D9
+:1094F0004118686881420ED9FFF7A8FC00280CD0FB
+:1095000029686868090109194A680989801AFCF7FD
+:109510002FFE002903D00720F8BD1020F8BD3B46E0
+:109520000022294604200096FEF78CFBF8BD0000BF
+:10953000E4200020B8270020F8B50026214C22485E
+:10954000A680667006704670867035462846FEF71F
+:1095500069FB6D1C0A2DF9D31C4F668078693D04A8
+:10956000401C05D0286981B27869FCF701FE00E053
+:1095700068692969801E89B24843A0601248A680A4
+:10958000CC3006600681868146810820607078694B
+:10959000401C05D0286981B27869FCF7E9FD00E03C
+:1095A00068692969401E89B24843296989B2FCF774
+:1095B000DFFD20DF002803D1034A012191702170D3
+:1095C000F8BD0000E4200020EC2600200010001070
+:1095D000FFB5264881B000781F4616460D46002884
+:1095E00008D0002D09D00198002806D0286800284E
+:1095F00018D104E0082005B0F0BD0E20FBE71C4C9C
+:10960000206800280ED0618920896268414389184A
+:109610006A68914206D9002E04D0B04202D3F119F3
+:10962000814201D90720E6E70198FFF70FFC0028E7
+:109630001AD03846FFF70AFC002815D06868FFF7F3
+:1096400005FC002810D068683246C1190198FCF763
+:10965000A5FD009628680022000124580321284611
+:10966000019BA0470020C6E71020C4E7E4200020AB
+:10967000B8270020F8B504463A480F46007800287D
+:109680001DD0002C1DD0002F1BD02068002818D022
+:1096900001252D07A688286980B2864230D8102E71
+:1096A0002ED3E088009000282AD02F484169491C19
+:1096B00009D02969406989B2FCF75AFD04E0082005
+:1096C000F8BD0E20F8BD68692969401E89B248437B
+:1096D000244B00999D6871434B19984210D3012087
+:1096E0000007006980B2814208DD0120000700699F
+:1096F000314680B2FCF73CFD002901D1B00701D012
+:109700000720F8BD17484288012A1AD0174824C7F5
+:1097100011010E18756023684350A0883081E088DD
+:109720007081A088E18800234843012109075B1C60
+:109730000C699BB2A4B2844205D20C69A4B2001B8E
+:1097400002E00420F8BD00200C69A4B265190C6980
+:10975000A4B28442EBD90348521C8560B381428095
+:109760000020F8BDE420002000100010B8270020E1
+:10977000F8B5044620481E46007815460F460028D6
+:1097800007D0002F07D0002C05D02068002817D163
+:1097900003E00820F8BD0E20F8BD18480168002934
+:1097A0000ED04289018940684A431018626890428D
+:1097B00006D9002D04D0A94202D3A819884201D9A4
+:1097C0000720F8BD3846FFF741FB002811D030468E
+:1097D000FFF73CFB00280CD06068FFF737FB002840
+:1097E00007D02B463A46214602200096FEF72AFA79
+:1097F000F8BD1020F8BD0000E4200020B8270020AC
+:10980000F8B5334B9978012914D1002531499D7061
+:109810004A78082A05D002280FD003280AD10D2043
+:1098200006E0022807D00D70FEF7ECF9002801D001
+:10983000FDF700F9F8BD4D70F6E7254814240378CC
+:109840002449634318188B885B1C9CB28C80037975
+:10985000001D022B05D14688A102B14201D3012788
+:1098600000E00027052B01D1072A03D00021042B9B
+:1098700002D003E00121FAE7072A03D00026042BD7
+:1098800002D007E00126FAE74088A302834201D311
+:10989000002A1AD00020314301433943C4D00D4E71
+:1098A00028467570FDF7C6F8094C2078FEF7BAF91E
+:1098B000B5806078401E60702078401CC0B2207077
+:1098C0000A28B1D30A382070AEE70120E3E7000090
+:1098D000EC260020E4200020F8B5044620481E466F
+:1098E000007815460F46002807D0002F07D0002C1F
+:1098F00005D02068002817D103E00820F8BD0E200D
+:10990000F8BD1848016800290ED0428901894068D5
+:109910004A4310186268904206D9002D04D0A9422B
+:1099200002D3A819884201D90720F8BD3846FFF7AD
+:109930008DFA002811D03046FFF788FA00280CD0A5
+:109940006068FFF783FA002807D02B463A46214685
+:1099500005200096FEF776F9F8BD1020F8BD00004E
+:10996000E4200020B827002010B511481149808854
+:10997000884207D0132176DF05E000221146FCF76C
+:1099800069FF02E074DF0028F7D10020FDF7FEFE3A
+:10999000002803D000221146FCF75CFFFDF764FBB2
+:1099A000002803D000221146FCF754FF10BD000030
+:1099B00000200020FFFF000010B5074800780028B5
+:1099C00009D0054805492C30FDF76CFD81B20348EC
+:1099D0007038FDF741FD10BD0020002064250020F7
+:1099E00070B501240B49600408600B494010886081
+:1099F000094940398860094D6C602F20FCF78CFBC9
+:109A0000AC60074D00242F206C61FCF785FB2C71A6
+:109A100070BD000080E100E04013014000100140F3
+:109A200024210020F0B500220123032636041F461E
+:109A30009740394212D0C5683D4203D101246404E5
+:109A40003D4301E03446BD43C560064D97007D1996
+:109A50002F68B7432F602F6827432F60521C202A9E
+:109A6000E5D3F0BD0007005010B50448012281780D
+:109A7000514081700248FDF706FD10BD0020002016
+:109A8000F424002070B51448012451216425846019
+:109A900022C0047110491148FDF7F8FC0E488C21D2
+:109AA00010300160FF212D3141600A2181600026C4
+:109AB0000673014609480830FDF7E8FC0648FF2117
+:109AC00020300560F531846041600673014603482B
+:109AD0001030FDF7DBFC70BD442500201C20002069
+:109AE000F7B582B002981446006B0F46002833D0B9
+:109AF000029800252030009028E00298016B0C208D
+:109B000068430E18217830794A1C2270C01C7854A2
+:109B100020781622411C21703A542078C1193088CF
+:109B200000F0DCFA21784018C0B22070B288002A18
+:109B300009D0B16800290FD0C019FCF72FFB20789D
+:109B40003179401820706D1C0098EDB2007DA8425C
+:109B5000D3D8002005B0F0BD0720FBE730B59FB09B
+:109B6000032118A80175142110A8FCF73EFB00245E
+:109B7000012510A8109405711DA81290601C010702
+:109B8000090F10A81031017344738473601C01071E
+:109B9000090F10A81031C1730020014610A80174EC
+:109BA00001463148FDF702FC002803D0002211468F
+:109BB000FCF750FE142115A8FCF717FB0120014605
+:109BC00018A8103141700120014618A81031817089
+:109BD0000020014618A8C1700120014618A81031C4
+:109BE000017110A815940576642118A817940170C6
+:109BF0001D4815A91838FDF77BF9002803D000226D
+:109C00001146FCF727FE40216846FCF7EEFA17A143
+:109C10006846FDF762FC0120014608A81031017773
+:109C20000020014608A841776846FDF7D7FA0028CA
+:109C300003D000221146FCF70DFE11481B901A9428
+:109C40001C9410A8058508481AA98030FDF714FA5D
+:109C5000002803D000221146FCF7FCFD0948FEF75E
+:109C600059FA1FB030BD0000F42400204E6F72641A
+:109C7000696353656D69636F6E647563746F7200B9
+:109C8000ED8001006999010010B508461146C046F3
+:109C9000C04610BD002803D00249C86000207047AC
+:109CA0000E2070474821002070B5002901D08C0794
+:109CB00001D0072070BD064C0125A16062800549D6
+:109CC000636010DF0028F5D12570162026DF70BDF7
+:109CD00048210020899C0100002803D00249086126
+:109CE000002070470E207047482100200020704758
+:109CF000FFB583B00F46594D069A059906466B7815
+:109D0000114303200191002B03D100290FD0012121
+:109D10006970059A697801249200240700920B006B
+:109D2000FCF7D0FB07320721345474833200022140
+:109D3000EEE74B484169491C05D02169406989B269
+:109D4000FCF716FA00E060692169401E89B24843B9
+:109D5000216909048A0C216989B2794321DF00282D
+:109D600012D102210FE0384620DF00280CD10599DE
+:109D7000002901D0032106E0069900291FD131787E
+:109D8000042968D020E0697007B0F0BD344841690B
+:109D9000491C05D02169406989B2FCF7E9F900E066
+:109DA00060692169401E89B241432069059A80B2E9
+:109DB000784321DF0028E7D10699002901D004214A
+:109DC000E1E7317804292ED00521DCE724484169F8
+:109DD000491C05D02169406989B2FCF7C9F900E046
+:109DE00060692169401E89B2484371884218009811
+:109DF0001018226992B27A435218009953180146FA
+:109E00001846069A21DF0028DBD0BDE7206971885B
+:109E100080B2784380188A08316921DF0028B3D1E5
+:109E20000199002917D00621ADE70D484169491C69
+:109E300005D02169406989B2FCF79AF900E06069B0
+:109E40002169401E89B24843216989B2FCF790F923
+:109E500020DF002898D1072195E70000E4200020AA
+:109E60000010001010B50446FFF7CAFC022C01D008
+:109E7000032C07D104484178002903D00021417008
+:109E8000FCF76EFC10BD00000020002070B50C0037
+:109E9000054604D1002211461046FCF7DBFC002DDC
+:109EA00004D1002211461046FCF7D4FC207828701B
+:109EB0002088FF2109020840000A68702068090212
+:109EC0000840000CA8702068000EE8702079287106
+:109ED000207A68712089000AA871A068000CE871D6
+:109EE00070BD0000012002490005086070470000B5
+:109EF00000E200E0F0B5174F2421BC6841430D1982
+:109F000039694A1C09D024224A4316196B687268C1
+:109F1000934204D8D21A726029623861F0BD0A46B1
+:109F200002E00A46396A9B1B4E1C0BD024264E4386
+:109F300037197E689E42F4D324264E43361977683B
+:109F4000FF1A77606B60296224214A431119086265
+:109F5000F0BD00002421002070B5194C2269A568CD
+:109F60001346114606E0814207D00A462426714373
+:109F70004919096A4E1CF6D170BD002EFCD08A42E8
+:109F80000CD1242043435819006A2061401C05D19C
+:109F90000C4B0120986000236361607124204143D1
+:109FA0004819242341685A43006A52191062421C1E
+:109FB000E2D024225043401942685118416070BDDC
+:109FC0002421002000100140F8B51D4C2569681CB3
+:109FD00035D01C48002640686169401A07023F0AD4
+:109FE00019E024204543A0682A185068B84214D8C4
+:109FF000A3693F1A8619156A002B09D0D1699069A7
+:10A000009847002807D000221146FCF723FC02E005
+:10A010009169D0698847681CE3D1A178E0788142D2
+:10A0200006D1401CC0B2E070022801D10020E070CF
+:10A0300003490006800D1C310E50FFF753FFF8BD99
+:10A04000242100200015014008B500201D4B0522E9
+:10A0500008210090FCF72AFD002803D000221146B9
+:10A06000FCF7F8FB184A01211848FCF7F9FC002816
+:10A0700003D000221146FCF7EDFB1448144A0121DD
+:10A08000001DFCF7EDFC002803D000221146FCF770
+:10A09000E1FB0E480F4A01210830FCF7E1FC0028E3
+:10A0A00003D000221146FCF7D5FB08480A4A0121DB
+:10A0B0000C30FCF7D5FC002803D000221146FCF739
+:10A0C000C9FB08BD5C210020E16D01000C200020CF
+:10A0D000D18B0100B9990100699A01000870000A4A
+:10A0E0004870022070470000012181400248426808
+:10A0F0008A43426070470000A820002038B5154907
+:10A10000154A488890420FD04A78144C521CD2B25B
+:10A110004A70237B934208D3083175DF002803D0AF
+:10A12000A169002900D0884738BD00254D70217CE9
+:10A13000002907D03B2176DF002803D0A169002940
+:10A1400000D0884761690029EED0684605708847CD
+:10A1500038BD0000C0200020FFFF0000B026002016
+:10A1600012494868C005C00D19D0103840B2002807
+:10A1700007DA0207120F083A920892005118C969CB
+:10A1800004E081080A4A8900891809688007C00E1E
+:10A19000C1400806800F012803D0032803D0022005
+:10A1A00070470020704701207047000000ED00E07C
+:10A1B00000E400E042788378521C934200D10022F0
+:10A1C0000378934201D1002070470A6041684078CB
+:10A1D0001822504308187047F8B51546069C1E46CD
+:10A1E00007460222009400F00AF8002806D1334600
+:10A1F000102229463846009400F001F8F8BDFFB55A
+:10A2000083B0074600200C9C8646267805463AE037
+:10A210007868A90041180A88684682804988C18008
+:10A220000022694601A865DF002810D16846017840
+:10A230000598814226D17046002801D0002200E016
+:10A2400002222078891841181F2902D90C2007B052
+:10A25000F0BD7146002908D1401CC0B2411C069BCC
+:10A26000049A21701A5401208646217806980A180B
+:10A27000694601A865DF0028E9D169462078097898
+:10A28000401820706D1C3888A842C1DC7046002838
+:10A2900004D020780699801B401E88550020D6E700
+:10A2A0000D1801000F1801000A180100110000002C
+:10A2B0001200000013000000140000001100030051
+:10A2C000B976010012000300B97601001300030003
+:10A2D000B976010014000300B976010023D1BCEA6D
+:10A2E0005F782315DEEF1212000000007F9101005D
+:10A2F000ED8A0100758A01009D8B01007B910100B0
+:10A30000BD8A0100718A0100958B010077910100DF
+:10A31000798A01006D8A01008D8B01003CA3010048
+:10A32000002000205C0100004C62010050A30100ED
+:10A330005C2100209C0E0000EE6401004201034CF1
+:10A34000FFFF038624F4021C010466118101000052
+:04000005000160C1D5
+:00000001FF
diff --git a/app/src/main/res/raw/ble_app_hrs_dfu_s110_v7_1_0_ext_init.dat b/app/src/main/res/raw/ble_app_hrs_dfu_s110_v7_1_0_ext_init.dat
new file mode 100644
index 000000000..0233236df
Binary files /dev/null and b/app/src/main/res/raw/ble_app_hrs_dfu_s110_v7_1_0_ext_init.dat differ
diff --git a/app/src/main/res/raw/ble_app_hrs_s110_v6_0_0.hex b/app/src/main/res/raw/ble_app_hrs_s110_v6_0_0.hex
new file mode 100644
index 000000000..472d637c8
--- /dev/null
+++ b/app/src/main/res/raw/ble_app_hrs_s110_v6_0_0.hex
@@ -0,0 +1,1231 @@
+:020000040001F9
+:10400000B03C0020C1410100D3410100D541010075
+:1040100000000000000000000000000000000000A0
+:10402000000000000000000000000000D741010077
+:104030000000000000000000D9410100DB41010048
+:10404000DD410100DD410100DD410100DD410100F4
+:10405000DD41010000000000DD410100DD41010003
+:10406000DD410100DD410100DD410100DD410100D4
+:10407000DD410100DD410100DD410100DD410100C4
+:10408000DD4101007D670100DD410100DD410100EE
+:104090009F670100DD410100056C0100DD41010069
+:1040A000DD410100DD4101000000000000000000D2
+:1040B0000000000000000000000000000000000000
+:1040C00000F002F800F040F80CA030C808382418BE
+:1040D0002D18A246671EAB4654465D46AC4201D140
+:1040E00000F032F87E460F3E0FCCB6460126334232
+:1040F00000D0FB1AA246AB46334318478C4A000057
+:10410000AC4A0000103A02D378C878C1FAD85207F6
+:1041100001D330C830C101D504680C60704700007D
+:104120000023002400250026103A01D378C1FBD8D3
+:10413000520700D330C100D50B6070471FB5C04691
+:10414000C0461FBD10B510BD04F0E6FC1146FFF7D8
+:10415000F5FF00F0D3FD04F0FEFC03B4FFF7F2FF1F
+:1041600003BC04F003FD0000401E00BF00BF00BF01
+:1041700000BF00BF00BF00BF00BF00BF00BF00BF47
+:1041800000BFF1D17047000070B505460C461646D9
+:1041900002E00FCC0FC5103E102EFAD2082E02D32B
+:1041A00003CC03C5083E042E07D301CC01C5361F3E
+:1041B00003E021782970641C6D1C761EF9D270BD55
+:1041C0000A4802680F210A430260094880470948EB
+:1041D0000047FEE7FEE7FEE7FEE7FEE7FEE700003A
+:1041E00005480649064A074B704700002405004071
+:1041F0009F420100C1400100B02C0020B03C0020D3
+:10420000B0340020B03400202E482F490860704799
+:104210002E48008CC0B2012810D12C48808C000799
+:10422000000F0BD12948806AF0210840402805D1B1
+:104230002648C06A084201D1012070470020FCE7EF
+:104240002248008CC0B2012827D12048808C00076A
+:10425000000F22D11D48806AF021084205D11B4879
+:10426000C06A084201D1012070471848806AF021D5
+:104270000840102805D11548C06A084201D1012024
+:10428000F2E71248806AF0210840302805D10F4833
+:10429000C06A084201D10120E6E70020E4E710B53A
+:1042A000FFF7CEFF002805D009480A494860C81327
+:1042B00009498861FFF7ACFF002802D001200749B7
+:1042C000886010BD0024F40000200020C00F00F022
+:1042D000DFFF07C000050040006C00400006004002
+:1042E00030B513048C0023430524240707252D0231
+:1042F00064198500635130BD0F2000F026FD00BF1A
+:10430000BFF34F8FFD48FE49C860BFF34F8F00BF1A
+:1043100000BFFEE704460D462A462146F948FFF74E
+:10432000EBFF70B5F849F94801F03BFEC5B22946EC
+:10433000F74801F078F80446002C0DD0082C0BD07B
+:10434000F448844208D0F448844205D000BFF3A268
+:10435000B7212046FFF7D0FF70BD10B50446FFF728
+:10436000E0FF10BD70B50646EF49F04801F019FEB8
+:1043700085B2EF480068401CED4908602946ED48C9
+:1043800001F040FA0446002C0DD0082C0BD0E14877
+:10439000844208D0E048844205D000BFDFA2E42177
+:1043A0002046FFF7A9FF0321E148006804F004FB61
+:1043B000002901D0012000E00020DF49087070BD15
+:1043C00070B50546DC480078002809D0DB49DC4898
+:1043D00001F0E7FD84B22146D64801F042FA00BF61
+:1043E00070BD10B50446D748007801214840D54932
+:1043F000087008460178CF4801F064FA10BD10B586
+:10440000082000F099FC092000F096FC0F2000F035
+:1044100093FC10BDF8B500BF0020CB4B0522114620
+:10442000009002F0C0F9054600BF2E46002E06D0CF
+:1044300000BFBAA2FF212C313046FFF75DFF00BF5D
+:1044400000BFC24A0121C24802F02CFA044600BF54
+:104450002546002D06D000BFB0A2FF2132312846EC
+:10446000FFF74AFF00BFBB4A0121BB4802F01AFA1E
+:10447000044600BF2546002D06D000BFA7A2FF219D
+:1044800037312846FFF738FF00BFB44A0121B4484E
+:1044900002F008FA044600BF2546002D06D000BFF2
+:1044A0009EA2FF213C312846FFF726FF00BFAD4A00
+:1044B0000121AD4802F0F6F9044600BF2546002D63
+:1044C00006D000BF95A2FF2141312846FFF714FF17
+:1044D00000BFF8BD3EB500BF68460078010909017C
+:1044E000491C009168460078F02188431030009004
+:1044F00000BF0A229DA168467CDF044600BF254616
+:10450000002D06D000BF85A2FF2155312846FFF7B8
+:10451000F3FE00BF984878DF044600BF2546002D13
+:1045200006D000BF7DA2FF2158312846FFF7E4FEE8
+:1045300000BF002001900290FF21913168468180E8
+:104540004900C18000210181FF219131418101A8F1
+:104550007ADF044600BF2546002D06D000BF6FA2BB
+:10456000FF2162312846FFF7C7FE00BF3EBD30B5D0
+:1045700093B006210391814A07CA6B4607C33C21C9
+:1045800004A804F017FA022004900121684641753E
+:10459000018303A907910321818569460C910021BC
+:1045A00004A801F065FC044600BF2546002D06D096
+:1045B00000BF5AA2FF2183312846FFF79DFE00BFAE
+:1045C00014216F4804F0F6F900206D490870486026
+:1045D000087228200882B420488213B030BD30B55C
+:1045E0009BB003210091142116A804F0E3F90020E8
+:1045F0001690012110A801776846189000BF18A8EE
+:10460000007901090901491C18A801710079F021FC
+:104610008843103018A9087100BF00BF16A8407B5E
+:104620000009000116A9487316A8407BF0218843B1
+:1046300016A9487300BF00BF16A8807B00090001BF
+:10464000887316A8807BF021884316A9887300BF61
+:1046500000BF18A8C07901090901491C18A8C17137
+:10466000C079F0218843103018A9C87100BF00BF7D
+:1046700016A8007C0009000116A9087416A8007C81
+:10468000F021884316A9087400BF2A4801F084F875
+:10469000044600BF2546002D06D000BF1FA2FF2103
+:1046A000AE312846FFF728FE00BF142111A804F000
+:1046B00081F900BF10A8407C01090901491C10A81C
+:1046C0004174407CF0218843103010A9487400BF29
+:1046D00000BF11A8807B00090001401C11A988734C
+:1046E00011A8807BF0218843103011A9887300BF86
+:1046F00000BF11A8C07B0009000143E00400FA05D7
+:1047000000ED00E0EFBEADDE6C24002014200020A0
+:104710000424002003300000013400002E2E5C6DC4
+:1047200061696E2E630000007C2400201C200020A4
+:10473000082000201C240020062000208C240020BB
+:10474000242000200C200020102100205B430100C9
+:104750002C2000206543010030200020C1430100CF
+:1047600034200020E3430100382000204E6F7264A3
+:1047700069635F48524D0000410300007C8B0100DB
+:10478000F0230020C87311A8C07BF021884311A931
+:10479000C87300BF00BF10A8007D01090901491CB2
+:1047A00010A80175007DF0218843103010A908750C
+:1047B00000BF00201190012110A801720020139069
+:1047C000642110A8017411A9F94800F00BFE0446F9
+:1047D00000BF2546002D06D000BFF64AFF21C0319C
+:1047E0002846FFF789FD00BF402101A804F0E2F848
+:1047F000F1A101A801F0A2FB00BF10A800780109F7
+:104800000901491C10A801700078F021884310307C
+:1048100010A9087000BF00BF10A84078000900016F
+:10482000487010A84078F021884310A9487000BF54
+:1048300001A800F0AFFB044600BF2546002D06D0BE
+:1048400000BFDC4AFF21CB312846FFF755FD00BFF2
+:104850001BB030BD10B55120DC49086064204860B1
+:10486000012088600873DA4801F08EFB8C20D9495A
+:104870000860FF202D3048600A20886000200873FF
+:10488000D54801F081FB6420D4490860FF20F53051
+:1048900048600120886000200873D14801F074FB53
+:1048A00010BD1E20CF49088008468078400840008F
+:1048B000401C88700846807802218843C949887066
+:1048C000084680781C2188430C30C6498870084609
+:1048D000807820218843C34988700720C870102041
+:1048E0000871704770B5002201210904BE480068B4
+:1048F00002F01FF8044600BF2546002D05D000BF7A
+:10490000AC4ABA492846FFF7F7FC00BF0022012154
+:10491000C903B748006802F00CF8044600BF2546FA
+:10492000002D06D000BFA34AB049C91C2846FFF796
+:10493000E3FC00BF0022AF49AF48006801F0F9FF77
+:10494000044600BF2546002D06D000BF994AA7495E
+:10495000891D2846FFF7D0FC00BF00220521C903AE
+:10496000A648006801F0E5FF044600BF2546002D7B
+:1049700006D000BF8F4A4121C9002846FFF7BCFC82
+:1049800000BF70BD70B59E4873DF044600BF25466A
+:10499000002D06D000BF874A944914312846FFF7FE
+:1049A000ABFC00BF082000F0D0F970BD70B5054623
+:1049B0002868002810D13B219248008876DF044601
+:1049C00000BF2646002E06D000BF7A4A87492B3109
+:1049D0003046FFF791FC00BF70BD044600BF754A2A
+:1049E000824936312046FFF787FC30B587B01C215D
+:1049F000684603F0DFFF002000900520C00301900F
+:104A000002900321684601737F4881896846C1810D
+:104A1000002101747D4805907D480690684602F0AB
+:104A200088F9044600BF2546002D06D000BF614A24
+:104A3000932189002846FFF75FFC00BF07B030BD17
+:104A400070B505462888102806D011280ED01328E6
+:104A500025D019284BD135E0092000F076F908203F
+:104A600000F078F9A8886749088041E0092000F043
+:104A700071F90020C0436349088003F011F804462F
+:104A800000BF2646002E06D000BF4A4A4D21C9006D
+:104A90003046FFF731FC00BFFFF774FF28E0514AB2
+:104AA0000021584800887FDF044600BF2646002EBC
+:104AB00006D000BF3F4A4D4972313046FFF71CFC1B
+:104AC00000BF15E0A879002810D1082000F042F9B5
+:104AD00031DF044600BF2646002E06D000BF354A0F
+:104AE00042497C313046FFF707FC00BF00E000BFC1
+:104AF00000BF70BD10B50446204602F094FF214669
+:104B0000414800F006FD2146294800F06EFB204692
+:104B100002F0E3F92046FFF793FF10BD10B50446FD
+:104B2000204603F0CAFB10BD70B500BF0023502221
+:104B30003849082002F01FF8054600BF2E46002E17
+:104B400006D000BF1B4A2949AE313046FFF7D4FBDF
+:104B500000BF00BF304802F043F8044600BF2546BE
+:104B6000002D06D000BF134A2049B2312846FFF776
+:104B7000C3FB00BF294802F03CF8044600BF2546AD
+:104B8000002D06D000BF0B4A1849B6312846FFF762
+:104B9000B3FB00BF70BD10B5032200210846FFF72C
+:104BA0009FFB032200210120FFF79AFB10BD0000AC
+:104BB000042400201C4701004E6F72646963536532
+:104BC0006D69636F6E647563746F72006C2400208E
+:104BD000142000207C2400201C2000208C24002095
+:104BE000242000200E2000202C200020FF010000A7
+:104BF00030200020662600003420002038200020CD
+:104C0000F0230020042000201C240020AD490100D6
+:104C1000DB490100A0230020F54A01001D4B0100E3
+:104C2000044600BF4E4A4F492046FFF765FBFEB5DC
+:104C300003F076FB044600BF2546002D06D000BFDA
+:104C4000474AB72189002846FFF756FB00BF0120DD
+:104C500045490969C1400140002900D100E0002018
+:104C6000064642484069401C08D0012212071269DA
+:104C700091B23E4A506903F09FFE02E00120000716
+:104C80004069401E6946087038484069401C08D099
+:104C900001221207126991B2344A506903F08CFE66
+:104CA00002E0012000074069C01EC1B268464170A1
+:104CB000002001902E4802906846867003F02EF87E
+:104CC000044600BF2546002D06D000BF244A2549D2
+:104CD0001B312846FFF710FB00BFFEBD70B53DDF5E
+:104CE000044600BF2546002D06D000BF1C4A1D49C2
+:104CF00025312846FFF700FB00BF70BDFFF77FFBA3
+:104D0000FFF749FFFFF710FFFFF791FFFFF782FB67
+:104D1000FFF7E0FBFFF72BFCFFF761FCFFF79AFDC5
+:104D2000FFF763FEFFF7BDFDFFF7DCFDFFF72AFE8F
+:104D300000BFFFF7D3FFFCE701210522120707237D
+:104D40001B02D2188300D150704701218140064ACE
+:104D50009160704701218140034AD1607047000093
+:104D60001C470100CE02000000050050001000109A
+:104D7000214C010070B504460D4600BF002D01D046
+:104D8000012000E000200646002E05D100BFE6A26B
+:104D90003321FFF7B1FA00BF00BF00BF002C01D0E4
+:104DA000012000E000200646002E05D100BFDEA253
+:104DB0003421FFF7A1FA00BF00BF287820702988AE
+:104DC0002879FF231B02194000200206080A10431D
+:104DD000607028791B022968194000200204080C21
+:104DE0001043A07029791B0228681840002109028D
+:104DF000000E0843E07028792071287A6071190C40
+:104E0000A8680840000AA0710902A8680840000CC0
+:104E1000E07170BDF8B505460E46002400BF002EB7
+:104E200001D0012000E000200746002F05D100BF7F
+:104E3000BDA24B21FFF760FA00BF00BF00BF002DED
+:104E400001D0012000E000200746002F05D100BF5F
+:104E5000B5A24C21FFF750FA00BF00BF32782046C0
+:104E6000611CCCB22A542919708800F054F9001939
+:104E7000C4B22919B08800F04EF90019C4B229193A
+:104E8000F08800F048F90019C4B200BF072C01D126
+:104E9000012000E000200746002F05D100BFA2A29C
+:104EA0005421FFF729FA00BF00BFF8BDF0B58FB05D
+:104EB00007460D4614461E4600BF002D01D00120B6
+:104EC00000E0002000900098002805D100BF96A2C5
+:104ED0006D21FFF711FA00BF00BF00BF002C01DDFC
+:104EE000012000E0002000900098002805D100BFBC
+:104EF0008DA26E21FFF700FA00BF00BF1C2107A89A
+:104F000003F058FD6946087F02218843801C6946EA
+:104F10000877002008900A900B900C900D9000BF2D
+:104F2000012108A881766846078700BF002001900C
+:104F30003178684601717178417180790621884322
+:104F4000811C684681718079082188430146684642
+:104F5000817180791021884301466846817180798A
+:104F60004108490068468171142102A803F022FD1E
+:104F70000EA8029001A80390684604820021418295
+:104F80008482069502AA07A974480088149BA2DFB0
+:104F90000FB0F0BD7FB5044600BF012168468173A4
+:104FA0006F49818100BF6D4A03A90120A0DF05463A
+:104FB000002D02D0284604B070BD208800280DDDE9
+:104FC00068480090228823463C3367486168FFF7B1
+:104FD0006DFF0546002D01D02846ECE7208900280A
+:104FE0000EDD62480090228923463C335E48401F14
+:104FF000E168FFF75BFF0546002D01D02846DAE7A0
+:10500000208A00280EDD5A480090228A23463C332D
+:105010005548001F6169FFF749FF0546002D01D083
+:105020002846C8E7208B00280EDD52480090228BCE
+:1050300023463C334C48801EE169FFF737FF0546A5
+:10504000002D01D02846B6E7208C00280EDD4A4806
+:105050000090228C23463C334348C01E616AFFF710
+:1050600025FF0546002D01D02846A4E7208D002805
+:105070000EDD42480090228D23463C333A48401EC4
+:10508000E16AFFF713FF0546002D01D0284692E79D
+:10509000206B002813D001A8216BFFF76BFE384866
+:1050A00023463C33082201A900902F48801FFFF7B8
+:1050B000FDFE0546002D01D028467CE700BF606B51
+:1050C00000280FD02F480090606B027923463C33B4
+:1050D00001682548401CFFF7E9FE0546002D01D078
+:1050E000284668E7A06B002813D001A8A16BFFF742
+:1050F00091FE254823463C33072201A900901A4817
+:105100002730FFF7D3FE0546002D01D0284652E791
+:1051100000BF00204FE702460A70FF200002104047
+:1051200000124870022070472E2E5C2E2E5C2E2E10
+:105130005C2E2E5C2E2E5C536F757263655C626C08
+:10514000655C626C655F73657276696365735C62EA
+:105150006C655F6469732E63000000003C200020D2
+:105160000A1800003E200020292A000046200020C6
+:105170004E200020562000205E20002066200020C7
+:105180006E200020762000207E2000208A88428227
+:1051900070470022D24342827047F8B504460E465B
+:1051A000207D002820D0B51D2988608981421AD130
+:1051B000288B022817D12068002814D000BF00BF18
+:1051C000A97EEA7E120211430846C107C90F0029D1
+:1051D00002D00020009001E00120009069462046A6
+:1051E0002268904700BF00BFF8BD70B505460C4669
+:1051F0002088102804D0112807D050280FD109E0AA
+:1052000021462846FFF7C2FF0AE021462846FFF75D
+:10521000C0FF05E021462846FFF7BFFF00E000BFC2
+:1052200000BF70BDF0B591B004460D46207D00284A
+:1052300019D00020099000BF08A8007901090901D0
+:10524000491C08A801710079F0218843103008A991
+:10525000087100BF697B08A84171807906218843E5
+:10526000801C08A988711C210AA803F0A3FB08A8C8
+:10527000007A02218843811C08A80172007A10215B
+:105280008843217D002901D0012100E0002109018E
+:1052900010221140084308A9087200200B900D90BD
+:1052A0000E90207D002801D009A800E000200F907A
+:1052B0000020109000BF01216846817377498181E9
+:1052C00000BF00200290A97B68460172E97B417211
+:1052D000807A06218843811C68468172807A082181
+:1052E0008843014668468172807A102188430146CE
+:1052F00068468172807A4108490068468172297B3C
+:105300000191142104A803F055FB03A8049002A8FE
+:105310000590012168460183002141830121818399
+:1053200001A80890A088A31D04AA0AA9A2DF064626
+:10533000002E02D0304611B0F0BDA86800284DD034
+:1053400000BF0121684681735549818100BF00205B
+:105350000290287C6946087200BF6846407A0109BD
+:10536000090168464172407AF021884369464872D3
+:1053700000BF6846807A06218843811C6846817296
+:10538000807A08218843014668468172807A10211C
+:105390008843014668468172807A41084900684620
+:1053A0008172A96800F0ACFD0746142104A803F03F
+:1053B00001FB03A8049002A805906846078300211A
+:1053C0004183018B81830890E08822460E3204A934
+:1053D000A3DF0646002E03D03046ACE70020E08174
+:1053E0000020A8E7F8B504460D4628682060002094
+:1053F000C043608228792075FF20207400BF0121FE
+:10540000684681702749018000BF221D694601203E
+:10541000A0DF0646002E01D03046F8BD29462046C2
+:10542000FFF700FFF9E733B585B004460025207C7F
+:105430006946097E88422FD0012104916846007E8A
+:105440002074E08806AB04AA0021A4DF0546002DE5
+:1054500002D0284607B030BD608A1349884219D06F
+:10546000207D002816D0002000900190029003902B
+:1054700001210491E18868460180012181700021A9
+:10548000818004A8029006A80390608A6946A6DF7E
+:10549000054600E0082500BF2846DBE7192A000082
+:1054A000082900000F180000FFFF00008A8802840E
+:1054B00070470022D2430284704738B505460C4637
+:1054C000208B022817D12868002814D000BF00BF05
+:1054D000A17EE27E120211430846C107C90F0029CE
+:1054E00002D00020009001E001200090694628468B
+:1054F0002A68904700BF38BD70B504460D46AE1D02
+:105500003188A089814203D131462046FFF7D5FF7B
+:1055100070BD70B505460C462088102804D01128AF
+:1055200007D050280FD109E021462846FFF7BEFFDB
+:105530000AE021462846FFF7BCFF05E02146284641
+:10554000FFF7DAFF00E000BF00BF70BDF7B582B023
+:10555000044617460020019001256079002803D0F9
+:1055600004210198084301902220005D002803D007
+:1055700002210198084301900398FF280ADD0121C8
+:105580000198084301907919039800F0AAF940198D
+:10559000C5B205E00398C2B22846691CCDB23A54A0
+:1055A0004C20005B002803DD102101980843019086
+:1055B000002618E0A81C14280BD94C20005B801B87
+:1055C0004200700023462433C118184603F0ACF99A
+:1055D0000DE07919720023462433985A00F081F9BE
+:1055E0004019C5B2761C4C20005BB042E2DC00BF23
+:1055F0004C20005B801B81B24C2001530198387015
+:10560000284605B0F0BD30B595B005460C460020E3
+:105610000D9000BF08A8007D01090901491C08A8D8
+:105620000175007DF0218843103008A9087500BF7E
+:10563000217B08A84175807D06218843811C08A82C
+:1056400081751C210EA803F0B5F908A8007E102171
+:105650008843103008A9087600200F90119012900E
+:105660000DA813900020149000BF01216846817797
+:10567000A049818300BF00200690617B68460176C7
+:10568000A17B4176807E06218843811C6846817615
+:10569000807E08218843014668468176807E1021FD
+:1056A0008843014668468176807E41084900491C4E
+:1056B00068468176142108A803F07CF907A80890B1
+:1056C00006A8099001AA00212846FFF73FFF0146DE
+:1056D00068460185002141851421818501A80C902F
+:1056E000E8882B46083308AA0EA9A2DF15B030BD02
+:1056F00030B58FB005460C461C2108A803F05AF9B6
+:1057000008A9087802218843801C08A90870002095
+:1057100009900B900C900D900E9000BF01216846EF
+:1057200081727449491C018100BF00200190E17B16
+:1057300068460171217C4171807906218843811C72
+:1057400068468171807908218843014668468171E5
+:10575000807910218843014668468171807941082B
+:10576000490068468171142103A803F023F902A8B7
+:10577000039001A804900121684681820021C18222
+:1057800001210183A0680790E8882B46103303AA03
+:1057900008A9A2DF0FB030BDF8B504460E46306848
+:1057A0002060307960710020C043208400212220D5
+:1057B00001554C20015300BF0121684681704E49BC
+:1057C000018000BFA21D69460120A0DF0546002D13
+:1057D00001D02846F8BD31462046FFF714FF0546A4
+:1057E000002D01D02846F5E7B068002808D03146E2
+:1057F0002046FFF77DFF0546002D01D02846E9E74A
+:105800000020E7E7F0B58BB004460F46208C3B49FB
+:10581000884222D006AA39462046FFF797FE064660
+:10582000059600200190029003900490218968461B
+:105830008180012181710021018105A8039006A8C2
+:105840000490208C01A9A6DF0546002D04D16846EE
+:10585000808AB04200D00C2500E0082528460BB015
+:10586000F0BD70B504460D464C20005B14280AD1EB
+:105870002622A118881E03F057F84C20005B401E1A
+:1058800081B24C2001534C20015B005B401C82B272
+:105890004C200253490020462430455270BD014639
+:1058A0004C20405A142801D1012070470020FCE709
+:1058B0000246108C114B984202D151710020704762
+:1058C0000820FCE722221154704713B582B0044629
+:1058D00001200190208A03AB01AA0021A4DF04B0BB
+:1058E00010BD02460A70FF200002104000124870EE
+:1058F00002207047372A00000D180000FFFF00004B
+:10590000F7B584B004460F4606980678B01C1F28E9
+:1059100003DC2079801C1F2802DD0C2007B0F0BDBD
+:105920001F20801B801E85B20295B11C781802A929
+:105930007DDF03900398002801D00398EEE72068EC
+:10594000022808D168460089A84204DC0920019099
+:10595000684605890BE0082001902079002804D0D2
+:105960002079A84201DC257901E068460589681C98
+:10597000C2B23046711CCEB23A543046721CD6B216
+:105980000199395406980178A81C0818C1B20698E4
+:1059900001700020C2E7F8B505460C462078001DCE
+:1059A0001F2801DD0C20F8BD684679DF0646002E71
+:1059B00001D03046F7E703212278501C2070A9540B
+:1059C00019212278501C2070A9542078411968466A
+:1059D000008800F08AFA2178401820700020E2E761
+:1059E000F8B505460F4616461C466868002801D1E2
+:1059F0000720F8BD2078801C298840181F2801DD69
+:105A00000C20F6E72878401CC1B22278501C207088
+:105A1000B1542178481C207077542A88237898192B
+:105A2000696802F01DFF2078297840182070002056
+:105A3000DFE730B503461078C01C1F2801DD0C20BD
+:105A400030BD02241578681C10704C550A24157856
+:105A5000681C10704C551578681C10704B55002050
+:105A6000EEE7FFB587B006461D46109C0020069065
+:105A700020780590002747E0B900706841180A882F
+:105A80006846028149884181002203A902A864DF97
+:105A900004900498002802D004980BB0F0BD68462A
+:105AA000017B099881422ED10698002801D0002060
+:105AB00000E00220019020786946097B4118019896
+:105AC00008181F2801DD0C20E7E70698002809D1F7
+:105AD0002078401C20702278511C08982170A8540E
+:105AE000012006902078421903A902A864DF0490DF
+:105AF0000498002801D00498CFE720786946097BF4
+:105B00004018207000BF7F1C3088B842B4DC069873
+:105B1000002806D021780598401C081AC1B20598C3
+:105B200029540020B9E7FFB581B0074615460A9E03
+:105B3000009602223846049B0299FFF792FF044622
+:105B4000002C02D0204605B0F0BD00961022294658
+:105B50003846049BFFF785FF0446002C01D0204601
+:105B6000F1E70020EFE701460888062808DB0888EF
+:105B70001922D201904205DD0888E04A904201D006
+:105B8000072070474888062808DB48881922D20178
+:105B9000904205DD4888D94A904201D00720F0E7BD
+:105BA0000888D64A904208D04888904205D0088894
+:105BB0004A88904201DD0720E3E70020E1E7F8B5DD
+:105BC00006460D4614462078801D1F2801D90C205A
+:105BD000F8BD3046FFF7C7FF0746002F01D0384613
+:105BE000F6E705212278501C2070A9541221227852
+:105BF000501C2070A95422785119308800F075F992
+:105C000021784018207022785119708800F06DF9C1
+:105C10002178401820700020DAE7F8B505460E46D6
+:105C200014462879801CC7B22078801CC0191F2810
+:105C300001DD0C20F8BD781CC1B22278501C207008
+:105C4000B154FF212278501C2070B1542278911950
+:105C5000288800F04AF9217840182070A888002888
+:105C60000EDDA868002801D10720E3E7AA88237881
+:105C70009819A96802F0F4FD20782979401820705D
+:105C80000020D7E7FEB507460E461446786B00287D
+:105C900001D10720FEBD0020019032E0796B0C227B
+:105CA000019850430D182879801CC0B200900098CC
+:105CB000401CC1B22278501C2070B15416212278A9
+:105CC000501C2070B15422789119288800F00DF9E9
+:105CD000217840182070A88800280EDDA8680028C8
+:105CE00001D10720D6E7AA8823789819A96802F07D
+:105CF000B7FD20782979401820700198401CC0B267
+:105D000001903820C15D01988142C7DC0020C1E7C5
+:105D1000F8B504460F46164600250020307020686E
+:105D2000002809D0324639462046FFF7E9FD0546EE
+:105D3000002D01D02846F8BD6079002808D03146F2
+:105D40003846FFF728FE0546002D01D02846F2E729
+:105D5000208900280BDD33463A46012120460830D1
+:105D6000FFF73EFE0546002D01D02846E3E72069F7
+:105D700000280BD000202169085632463946FFF72B
+:105D800058FE0546002D01D02846D4E7A08A0028F9
+:105D90000CDD3B4606220221204614300096FFF718
+:105DA000C2FE0546002D01D02846C4E7A08B00287E
+:105DB0000CDD3B460722032120461C300096FFF7EE
+:105DC000B2FE0546002D01D02846B4E7A08C00287D
+:105DD0000CDD3B4615221421204624300096FFF7A7
+:105DE000A2FE0546002D01D02846A4E7E06A00285F
+:105DF00009D032463946E06AFFF7E1FE0546002D3C
+:105E000001D0284697E7206B002809D03246394652
+:105E1000206BFFF702FF0546002D01D028468AE7D8
+:105E20003820005D002809DD324639462046FFF75C
+:105E300029FF0546002D01D028467CE728467AE751
+:105E400001460889002807D0C868002804D0C8681F
+:105E500000780422104201D1072070470020FCE79F
+:105E600001460889002801DD072070470020FCE773
+:105E7000F3B593B00D4600211291119113980028AB
+:105E800014D01398FFF7DCFF0446002C02D0204604
+:105E900015B0F0BD12AA09A91398FFF739FF0446FF
+:105EA000002C01D02046F3E709AE00E00026002DCB
+:105EB00013D02846FFF7D4FF0446002C01D020461B
+:105EC000E6E711AA01A92846FFF722FF0446002CA5
+:105ED00001D02046DCE701AF00E0002710A80379DD
+:105EE0003A46017A304672DFD2E702460A70FF2056
+:105EF000000210400012487002207047FFFF0000AF
+:105F0000F8B505460E46002432782146641C6A54D2
+:105F100072782146641C6A5400BF022C01D1012012
+:105F200000E000200746002F05D100BF08A2202175
+:105F3000FEF7E2F900BF00BF2046F8BD70B5044689
+:105F40000D46284602F06AFC2080656070BD0000A6
+:105F50002E2E5C2E2E5C2E2E5C2E2E5C2E2E5C5356
+:105F60006F757263655C626C655C626C655F7365BE
+:105F70007276696365735C626C655F7372765F638A
+:105F80006F6D6D6F6E2E63000A7B002A04D04A6825
+:105F900002600022027103E00A68026001220271BD
+:105FA00070470246107900280FD048681368C01A5D
+:105FB0008B68984204D910688B68C018106013E091
+:105FC00048681060002010710EE010680B68C01A5D
+:105FD0008B68984204D910688B68C01A106003E07F
+:105FE00008681060012010711068704710B5002813
+:105FF00019DAFE4A03071B0F083B9B089B00D25887
+:106000008307DC0EFF23A3409A438B071B0E8407F4
+:10601000E40EA3401A43F54B0407240F083CA408E0
+:10602000A4001A5118E0F24A03231B02D218830875
+:106030009B00D2588307DC0EFF23A3409A438B07B3
+:106040001B0E8407E40EA3401A43E94B03242402E9
+:106050001B198408A4001A5110BD10B50446E54868
+:10606000846003211120FFF7C1FF10BD10B501208E
+:106070000004E1494860E04940394860112000F0DF
+:10608000D4FC112000F0C3FC0120DC4908602F2063
+:10609000FEF76AF810BD10B5112001218140D44AE5
+:1060A0008032116000BF01200004D3498860D249CA
+:1060B000403988600120D14948602F20FEF754F80C
+:1060C00010BDF0B5024628205043CD4B1B68C118C7
+:1060D000CC480068401C02D1CA48026057E0C94859
+:1060E0008B68006828246043C54C246800198068C8
+:1060F000834217D8C348006828235843C04B1B6805
+:10610000C01880688B68C31ABE48006828246043A2
+:10611000BB4C246800198360BA4800684862B948DB
+:10612000026034E08B68B74D2C6828680DE02825A4
+:106130004543B34E3668AD19AD685B1B0446282550
+:106140004543AF4E3668AD19686A451C07D028250F
+:106150004543AB4E3668AD19AD689D42E7D3451C4B
+:106160000CD028254543A64E3668AD19AD68EE1A09
+:1061700028254543A24F3F68ED19AE608B60486209
+:10618000282565439E4E3668AD196A6200BFF0BD92
+:1061900070B502469B4C2168084609E0904200D148
+:1061A00008E0014628244443954D2D686419606A2F
+:1061B000441CF3D100BF441C00D170BD814209D101
+:1061C000904C246828256C438D4D2D686419646AB1
+:1061D0008C4D2C6028244443894D2D686419A36894
+:1061E00028244443864D2D686419656A28244C434D
+:1061F000834E3668A419656228244C43804D2D686F
+:106200006419606A441C0CD0282444437C4D2D68DA
+:106210006419A468E51828244443794E3668A41903
+:10622000A56000BFC9E710B5112000F0F7FB10BD55
+:1062300010B5142000F0F2FB10BD70B50446724892
+:106240000068002813D0704A216AE06912689047FC
+:10625000054600BF2E46002E07D000BF6BA2FF21CF
+:1062600055313046FEF748F800BF00BF02E0E16953
+:10627000206A884770BDF8B562480068401C39D074
+:10628000002700F004FC04466C480168204600F03A
+:1062900001FC06465B48056811E028206843584920
+:1062A00009684418A068B04200D90AE0A068361A0C
+:1062B000A068C719656A2046FFF7BFFF00BF681CCA
+:1062C000EBD100BF5E4800785E490978884209D169
+:1062D0005C480078401CC0B25A490870022801D1BD
+:1062E0000020087057480078800057490F50FFF78A
+:1062F0009FFF00BFF8BD014651480078514A12780F
+:1063000090421DD04E480078401C4D4A10701046F7
+:106310000078022801D100201070494800788000E0
+:10632000494A10580860454A086812688018434A6C
+:106330001060104600680002000A106001207047DB
+:106340000020086000BFFAE7FEB52E480068019003
+:106350003E48007800903FE00098C0003C49096842
+:106360004418257834E01820684361680F18681CC9
+:10637000C5B2A078A84200D100253868022802D012
+:10638000032822D10EE02821786848431C49096877
+:106390004618307E002804D07868FFF7F9FE002008
+:1063A000307613E00CE01749096828225143144A5B
+:1063B0001268881800210176124A416A116000BFF4
+:1063C00010480068401CEED100E000BF00BF00BFD5
+:1063D0006078A842C7D100BF0098411EC9B20091A1
+:1063E0000028B9D107480168019833E01CED00E0AE
+:1063F00000E100E0001501404013014000100140A1
+:106400008820002094200020A82000202E2E5C2E22
+:106410002E5C2E2E5C2E2E5C2E2E5C536F757263BE
+:10642000655C6170705F636F6D6D6F6E5C617070E5
+:106430005F74696D65722E63000000009820002073
+:10644000A4200020A52000209C2000208D200020DA
+:1064500090200020814201D00120FEBD0020FCE7F9
+:10646000F0B50346002426E0FE4E366828277E431A
+:10647000FD4F3F68F01986689E4203D98668F61A78
+:1064800086601CE086689B1B86683419002686603F
+:106490000676F44E3568F34F466A3E604669002E34
+:1064A00008D00E193602360AC660466906611668BB
+:1064B0004662156000BFEB480068401CD4D100BFA5
+:1064C000F0BDFEB50746E74800680290E74800784F
+:1064D000019069E00198C000E549096845185CE051
+:1064E000781C07D03E4628207043DF4909684418CD
+:1064F000676A23E02978182359436A685018297875
+:10650000491C29702978AA78914201D1002129706B
+:10651000466828217143D44A12688C180168012901
+:1065200002D1217E002900D037E08168E160C16896
+:106530002161016961614169216200BFCD49E06863
+:106540000968401A0002000ACB49884208D2C949AA
+:10655000E068096800F09EFA21694018A06011E027
+:10656000C448E168006800F095FA0090216900983D
+:10657000814204D921690098081AA06001E0002036
+:10658000A06000BF0020E060206101202076801E16
+:1065900060623046FFF795FD00BF781CA0D12878D7
+:1065A000697888429CD100BF0198411EC9B201910F
+:1065B00000288FD1AB4801680298814201D00120A8
+:1065C000FEBD0020FCE7FEB50546A6480068401C5D
+:1065D00034D0A448006828214843A34909684018DA
+:1065E000866800F054FA0290A24804682146029896
+:1065F00000F050FAC01C0190681C01D1FFF736FD75
+:106600000198B04201D2304600E0019804192402FA
+:10661000240A20469949086000BF00F038FA00902B
+:106620000299009800F036FAC71C2046029900F043
+:1066300031FA874201D9FFF7F6FD01E0FFF72BFDA4
+:10664000FEBDFEB50020C04301908A48046885481D
+:1066500007686846FFF74FFE0546FFF775FE0646DA
+:10666000002D05D001AA21460098FFF7F9FE01266A
+:106670000198FFF726FF002800D00126002E02D047
+:106680003846FFF7A0FFFEBD4170704770B5024667
+:106690000B465078411C9078884200D10021107838
+:1066A000884201D1002070BD1960507818267043CF
+:1066B00055682C182046F6E7FFB581B005460E4612
+:1066C0001746E9006A4A126888186946FFF7DEFF34
+:1066D0000446002C02D1042005B0F0BD012020604A
+:1066E000666000F0D4F9A060E760049820610A9821
+:1066F0006061E9005E4A126888180099FFF7C4FFDC
+:10670000FFF796FD0020E7E7F8B504460E46E100E6
+:10671000574A126888186946FFF7B8FF0546002DEA
+:1067200001D10420F8BD022028606E60E100504ACB
+:10673000126888180099FFF7A7FFFFF779FD00207E
+:10674000F0E738B50446E100494A126888186946FE
+:10675000FFF79CFF0546002D01D1042038BD032022
+:106760002860001F6860E100414A1268881800999B
+:10677000FFF78AFFFFF75CFD0020EFE710B5002070
+:106780003F49086000213E4841608160C1600020AF
+:106790003B49403908604860FFF76DFD10BD10B5FA
+:1067A000FFF74FFF10BDFFB581B00E4617461C46E0
+:1067B00020468107890F01D1012100E00021002935
+:1067C00002D1072005B0F0BD002C01D10720F9E768
+:1067D000FFF761FC2B490A9808602B480670224895
+:1067E000046000250CE00021282068431E4A12683E
+:1067F0001150282068431C4A1268801801766D1CCD
+:10680000B542F0DB282070430419032017490870B3
+:1068100017480460183400250CE0E900144A126897
+:1068200088180021017041708770446018217943F5
+:106830000C196D1C032DF0DB0020C04309490860D2
+:1068400000201249087012490870142000F0EDF879
+:1068500003211420FFF7CAFB142000F0D8F8019898
+:1068600018E0000094200020882000208D200020C7
+:106870009020002098200020FFFF7F00401501405D
+:1068800040110140A82000208C200020A4200020DE
+:10689000A5200020FFF7E1FB00F0F9F8804908602F
+:1068A00000208FE770B503460C467E48006800283C
+:1068B00001D1082070BD002A01D10720FAE7002B82
+:1068C00001D10720F6E700211CE028204843754D40
+:1068D0002D682858002814D1012528204843714EDE
+:1068E00036683550282048436E4D2D684019446065
+:1068F000282048436B4D2D684019C2611960002063
+:10690000D8E7491C684800788142DEDB0420D1E7E3
+:1069100010B500F092F8012802D0032804D101E05C
+:10692000002403E0012401E0022400BF00BF204650
+:1069300010BDFEB504460D4616465A4800680028AC
+:1069400001D10820FEBD58480078844201D2052DAF
+:1069500001D20720F6E72820604352490968085809
+:10696000012801D00820EDE7282060434D4909683F
+:1069700040184068012801D1284600E00020074661
+:10698000FFF7C6FF3B462A46214600960190FFF7D7
+:1069900093FED7E770B5044642480068002801D14D
+:1069A000082070BD40480078844201D30720F8E7F2
+:1069B000282060433B4909680858012801D0082075
+:1069C000EFE7FFF7A5FF05462146FFF79DFEE8E745
+:1069D00010B534480068002801D1082010BDFFF729
+:1069E00097FF0446FFF7ADFEF8E710B5044600F048
+:1069F0004EF82060002010BD70B505460E461446C6
+:106A00003146284600F046F82060002070BDC206DE
+:106A1000D20E01219140254A11607047C206D20E64
+:106A200001219140224A11607047C206D20E012115
+:106A300091401F4A80321160704710B51D48406870
+:106A4000C105C90D002920D00A46103A1046002879
+:106A50000DDA184B1C330407240F083CA408A400CB
+:106A60001B598407E40EE3401B069B0F0BE00F4B02
+:106A7000032424021B198408A4001B598407E40E74
+:106A8000E3401B069B0FD8B210BD0420FCE70A4868
+:106A9000406870470246501A0002000A7047000022
+:106AA00098200020882000208C20002000E100E0B9
+:106AB00000E200E000ED00E00015014070B5064680
+:106AC0000C46154629462046FDF724FC70BDF8B556
+:106AD00059480078002800D1F8BD584800680028BF
+:106AE00001D1012000E0002005465548006800283B
+:106AF00001D1012000E00020064600BF002D14D186
+:106B000068464EDF0446052C01D101250CE0002C1F
+:106B100006D000BF4BA26F212046FDF7EDFB03E03E
+:106B2000464900980968884700BF002E1AD153488B
+:106B30000088009069465248006860DF0446052CD2
+:106B400001D101260DE0002C06D000BF3DA2862118
+:106B50002046FDF7D1FB04E0494800683849096840
+:106B6000884700BF002D02D0002E00D000E0C5E70E
+:106B700000BF00BFB0E7FFB581B00C4616461F4608
+:106B8000002C02D1072005B0F0BD20468107890FF7
+:106B900001D1012100E00021002901D10720F2E705
+:106BA0003748046035480680364807603649019802
+:106BB00010DF0546002D01D02846E4E701201E49DC
+:106BC0000870162023DFDEE710B511DF0446002C25
+:106BD00001D0012000E0002017490870204610BDB8
+:106BE0000146002901D10E207047154801600020A0
+:106BF000FAE70146002901D10E2070470F480160D5
+:106C00000020FAE770B51F480068002811D01D4821
+:106C100000688047044600BF2546002D07D000BF0E
+:106C200008A2FF2139312846FDF766FB00BF00BFEF
+:106C300001E0FFF74CFF70BDAC200020C020002019
+:106C4000BC2000202E2E5C2E2E5C2E2E5C2E2E5C68
+:106C50002E2E5C536F757263655C73645F636F6D3A
+:106C60006D6F6E5C736F66746465766963655F688B
+:106C7000616E646C65722E6300000000B820002015
+:106C8000B4200020B0200020BD6A010001464888E1
+:106C9000B74A1288904206DB4888B54A528890422B
+:106CA00001DC012070470020FCE738B50546B148FB
+:106CB0000088B14988423BD0B0480078401CAF49B9
+:106CC0000870AF48007B097888420FDBA849A948C3
+:106CD000008875DF0446002C07D0A9488069002889
+:106CE00003D0A74881692046884721E00020A349B6
+:106CF0000870A348007C00280FD03B219D480088E5
+:106D000076DF0446002C07D09D488069002803D018
+:106D10009B4881692046884700BF99484069002800
+:106D200006D000200090964841696846884700BF19
+:106D300038BD70B505461C2229469148FDF724FA56
+:106D4000002090490870286800280BD00822884845
+:106D5000296801F085FD86487ADF0446002C08D0BA
+:106D6000204670BD82487BDF0446002C01D02046BF
+:106D7000F7E70020C0437F49088000207F49087062
+:106D8000814A00218148FFF78DFDEAE710B57F4871
+:106D90000068FFF7FFFD10BD38B57D48FFF776FFAF
+:106DA000002829D17748007800280AD07448406923
+:106DB000002820D00020009071484169684688472B
+:106DC00019E06E480078002802D16D48456801E05E
+:106DD0006B488568002229466C480068FFF7A9FDCA
+:106DE0000446002C07D066488069002803D0644818
+:106DF0008169204688470AE061484069002806D03A
+:106E0000012000905E4841696846884700BF002025
+:106E10005C49087038BD10B50446A0885549088003
+:106E2000082221460E315A4801F01AFD002053492C
+:106E300008705348C089002801D1FFF7ADFF10BD8D
+:106E400070B505460020C0434A49088000204B49E0
+:106E500008704E480068FFF79DFD0446002C07D0DF
+:106E600047488069002803D0454881692046884703
+:106E700070BD70B50546AC1D20884149C98988425E
+:106E800020D1208B02281DD100BF00BFA17EE27E51
+:106E9000120211430846C107C90F002902D0FFF7AB
+:106EA0007BFF0FE039480068FFF774FD0646002EAF
+:106EB00007D033488069002803D0314881693046C3
+:106EC000884700BF70BD10B504460822A11D304898
+:106ED00001F0C6FCFFF760FF10BD10B50446208826
+:106EE000102806D0112808D012280ED0502810D112
+:106EF00007E02046FFF78FFF0CE02046FFF7A0FFDA
+:106F000008E02046FFF7B5FF04E02046FFF7DBFF6F
+:106F100000E000BF00BF10BD38B50446082221467E
+:106F2000134801F09DFC12487ADF0546002D1CD164
+:106F30001748FFF7ABFE00280BD10120114908705C
+:106F40000B490C48008875DF054601200B49087085
+:106F50000BE00B484069002806D00120009008484B
+:106F600041696846884700BF0025284638BD0000B3
+:106F7000C6200020D0200020FFFF0000CE200020EF
+:106F80009C240020C4200020AB6C0100DC200020E9
+:106F9000D22000200246100C0004F94B984202D186
+:106FA0000A8000207047501C01D10520FAE70B2011
+:106FB000F8E77CB50546F34800780A2801DB042091
+:106FC0007CBDF0480078002806D10022114610460A
+:106FD00001F0D4FBEC4908806A46EA480178EB48A6
+:106FE00001F07AFA0446002C01D02046E8E76A4610
+:106FF000E4480178E54801F06FFA0446002C01D01E
+:107000002046DDE704234C222946684601F0A8FA11
+:107010000446002C01D02046D2E7DB4A4C2128460A
+:1070200001F0ACFBD849088008460088D4490843E1
+:10703000D44909788900D64A5050D2480078800057
+:10704000811800230422684601F08AFA0446002CC5
+:1070500001D02046B4E7CB480078401CC9490870ED
+:107060000020ADE77CB50546CA4800780A2801DB58
+:1070700004207CBDC7480078002806D100221146B4
+:10708000104601F07BFBC44908806A46C14801787C
+:10709000C24801F021FA0446002C01D02046E8E75E
+:1070A00004231C222946684601F05AFA0446002CA3
+:1070B00001D02046DDE7B84A1C21284601F05EFBDE
+:1070C000B549088008460088AD490843B1490978A8
+:1070D0008900B34A5050AF480078800081180023DF
+:1070E0000422684601F03CFA0446002C01D02046F8
+:1070F000BFE7A8480078401CA64908700020B8E700
+:107100007FB505469F480078002806D10022114629
+:10711000104601F033FB9C49088002AA9948017887
+:107120009A4801F0D9F90446002C02D0204604B058
+:1071300070BD0023042202A901A801F06FFA0446E1
+:10714000002C01D02046F2E70023042202A901A866
+:1071500001F064FA0446002C01D02046E7E76946B6
+:107160000198FFF717FF0446002C01D02046DEE708
+:1071700004234C2202A9284601F050FA0446002CB0
+:1071800001D02046D3E704234C2202A9284601F06F
+:1071900045FA0446002C01D02046C8E77A4A4C2123
+:1071A000284601F0EBFA78490880084600886946CD
+:1071B0000988884206D173480078401C71490870DC
+:1071C0000020B4E70B20B2E77FB505467148007890
+:1071D000002806D100221146104601F0CFFA6E4970
+:1071E000088002AA6B4801786C4801F075F90446E2
+:1071F000002C02D0204604B070BD02AA6548017878
+:10720000664801F069F90446002C01D02046F2E7F7
+:107210000023042202A901A801F000FA0446002C70
+:1072200001D02046E7E70023042202A901A801F0CB
+:10723000F5F90446002C01D02046DCE769460198A8
+:10724000FFF7A8FE0446002C01D02046D3E7042314
+:107250001C2202A9284601F0E1F90446002C01D0C5
+:107260002046C8E704231C2202A9284601F0D6F9CB
+:107270000446002C01D02046BDE7474A1C21284681
+:1072800001F07CFA4449088008460088694609886C
+:10729000884206D13F480078401C3E4908700020D3
+:1072A000A9E70B20A7E710B50A21384801F014FA26
+:1072B0000446002C04D10A21384801F00DFA044696
+:1072C000204610BD38B500221146104601F056FA8E
+:1072D0000090002409E068216143324A88186A4618
+:1072E0004C2101F04BFA0090641C2F480078844236
+:1072F000F1DB6846008824490988884201D00120D2
+:1073000038BD0020FCE738B500221146104601F0D8
+:1073100035FA009000240BE068216143214A891866
+:1073200008464C306A461C2101F028FA0090641C83
+:107330001D4800788442EFDB6846008816490988BA
+:10734000884201D0012038BD0020FCE770B5054619
+:107350001648406E002808D01448406E142801D901
+:10736000032070BD114C503400E000240F484C3015
+:10737000008B82B221460E480088A9DFF1E70000A9
+:1073800000002453FC200020E8200020EC200020F6
+:10739000AC280020FD200020EA200020F42000205E
+:1073A000C828002064250020E4200020FC240020C0
+:1073B000E220002010B500210020FE4A1070FE4A95
+:1073C000107032E068224A43FC4BD018427A5207D0
+:1073D000D20F12D0027852B2F74B1B78DB00F84C78
+:1073E000E25402461E32F44B1B78DB001B195A6034
+:1073F000F14A1278521CF04B1A704222125C022A97
+:1074000012D0027852B2EB4B1B78DB00ED4CE25409
+:1074100002464232E74B1B78DB001B195A60E54AF3
+:107420001278521CE34B1A70491CE74800788142DD
+:10743000C8DB10BD7CB50546E3480078072801DBB2
+:1074400004207CBD32222946E04801F009FAE9888F
+:10745000DE48001FC18700214166DB480078DB4918
+:10746000091F0860D8490978D74A1278521CD64BB0
+:107470001A7068225143D14A88186822D349091FDB
+:10748000FCF782FEFFF796FF0120D1490870002229
+:107490001146D0480088A9DF0446002C01D02046C0
+:1074A000CFE7CA48001FFFF784FD0446042C15D11E
+:1074B000C9484068002811D004200090C348001F2C
+:1074C000007841B268460171C048001FC18F68460C
+:1074D000C180C14841686846884703E0002C01D05C
+:1074E0002046AEE7BC484068002811D0002000903C
+:1074F000B648001F007841B268460171B348001FCA
+:10750000C18F6846C180B44841686846884700BF5B
+:10751000002096E770B5AD48001F0468002C23DBFF
+:10752000A948007884421FDAA9480078002808D0CA
+:1075300068216143A14A88184C22A449091FFCF71D
+:1075400023FEA648007800280AD0682161439B4AA0
+:10755000891808464C301C229C494831FCF714FE1F
+:10756000FFF728FF002500E00325284670BD70B511
+:107570000546002412E06820604390494018C08FFF
+:10758000A8420AD1682060430A46811868228F48C1
+:10759000001FFCF7F9FD002070BD641C8A480078CC
+:1075A0008442E8DB0520F7E770B594B000208649F7
+:1075B00008702FE001A8FFF7A3FD0546052D00D1B7
+:1075C0002CE0002D02D0284614B070BD019E7E48EC
+:1075D0000078864201DD0B20F6E768217143774A87
+:1075E00088184C2201A9FCF7CFFD77480078864225
+:1075F0000FD1002168207043704A80184166491EEF
+:10760000682070438018C1646F480078401C6E4940
+:10761000087000BF6C4800780728CBDB00BF00244F
+:107620001DE00DA8FFF7D0FD0546052D00D11AE09D
+:10763000002D01D02846C7E7634909780D98884294
+:1076400001DD0B20C0E768220D9951435B4A891880
+:1076500008464C301C220DA9FCF796FD641C5A48C4
+:1076600000788442DDDB00BF5748047810E0002139
+:10767000C94368206043514A11500021682060438B
+:1076800080184166491E682060438018C164641CEC
+:10769000072CECDBFFF78EFE002095E77CB5044657
+:1076A000A0884C4908800020C0434849091F086051
+:1076B0000722A11D45483E3001F0D2F80021434881
+:1076C000001F4166607BC007C00F1AD0607B450871
+:1076D000072D04DAE8003A490856072804DB3E483B
+:1076E0008168032088470BE0E800354A105668227D
+:1076F0005043324A811868223448001FFCF744FD89
+:1077000021E0002519E0E8002E49401846680622CD
+:107710002E493F31701C01F053F800280CD1E800CD
+:10772000284A105668225043244A81186822274864
+:10773000001FFCF729FD04E06D1C1E4800788542FF
+:10774000E1DB00BF00BF2148001F0068401C1AD0C9
+:1077500000201F490870214908701F484068002810
+:1077600011D0012000901948001F007841B26846EE
+:1077700001711648001FC18F6846C18016484168D4
+:107780006846884700BF7CBD70B505460F48001F9E
+:107790000068401C0AD012480078C007C00F05D00E
+:1077A0000F4800780221084040081FD000221146EF
+:1077B000084817E0E5200020E6200020642500208E
+:1077C000742800203C280020E42000200025002010
+:1077D000FF200020E2200020F024002000210020D3
+:1077E000FE2000200088A9DF044608E0FE48FFF7DD
+:1077F000ADFD0446002C02D10120FC490870002C8C
+:1078000003D0FB4881682046884770BD7CB504469C
+:1078100032222146F448001D01F022F8E188F248A6
+:10782000C1870146096868225143F24A88186821D5
+:1078300001F0C0F8EC49096868225143ED4A881804
+:107840006822E949FCF7A0FCE748FFF7B2FB0546D0
+:10785000042D13D1E648406800280FD00420009082
+:10786000E148007841B268460171DF48C18F68463F
+:10787000C180DF4841686846884705E0002D03D095
+:10788000DB48816828468847D948406800280FD0DF
+:1078900003200090D448007841B268460171D24874
+:1078A000C18F6846C180D24841686846884700BF9A
+:1078B0007CBD70B504462078002800D070BDCE484D
+:1078C0000078C007C00F00D1F8E7C7480068401C27
+:1078D00012D1E088FFF74BFE0546002D01D10F25A0
+:1078E00003E02046FFF7A6FD0546002D03D0C04863
+:1078F00081682846884705E00120C0490870204675
+:10790000FFF784FF00BFD9E770B50546E889FFF7A8
+:107910002EFE0446002C10D10120B8490870002228
+:10792000B1490A31B648008881DF0446002C1ED0D8
+:10793000AF4881682046884719E0052C17D10C22F2
+:10794000A91DA948363000F08BFF00221146AC4833
+:10795000008881DF0446002C03D0A54881682046BA
+:10796000884700221146A6480088A9DF0446002C5B
+:1079700003D09F4881682046884770BDFEB5044605
+:1079800020780607360F20780709012E01D1012F34
+:1079900003DC022E2CD1002F2AD000BF964800789D
+:1079A000012108439449087000BF8F48FFF7CEFCBF
+:1079B0000546002D04D08E4881682846884702E09D
+:1079C00001208A4908708A48406800280FD00220A8
+:1079D00000908548007841B2684601718248C18FA5
+:1079E0006846C180824841686846884700BF00BF3A
+:1079F000FEBD10B50446A078C007C00F12D000BF6E
+:107A00007D480078022108437B49087000BF764812
+:107A10000068401C06D00021734841661421503094
+:107A200000F0C8FF10BD10B5044676480078002865
+:107A300003D16F488168082088472088142820D007
+:107A400006DC10280BD011280DD0132825D11CE0FE
+:107A5000172812D018281CD052281ED109E0204621
+:107A6000FFF71CFE1AE000BF00206349087000BF4A
+:107A700014E02046FFF788FE10E0A01DFFF719FF75
+:107A80000CE02046FFF740FF08E0A01DFFF7B1FF24
+:107A900004E0A01DFFF772FF00E000BF00BF10BDB3
+:107AA00038B500BF57480078002801D1082038BDFC
+:107AB00000BF4D480068401C1CD0142000906A464E
+:107AC000494950314E480088AADF0446002C01D0B5
+:107AD0002046ECE7444800684349C864684601888A
+:107AE00041484166FFF716FD0446002C01D02046B0
+:107AF000DDE700BFFFF7E6FB00281DD00A21424862
+:107B000000F0EAFD0446002C01D02046CFE700201B
+:107B10003E49087000250BE068216943354A881802
+:107B2000FFF747FA0446002C01D02046BFE76D1C42
+:107B3000374800788542EFDBFFF7E5FB00281FD0D0
+:107B40000A21344800F0C8FD0446002C01D020462C
+:107B5000ADE700203049087000250DE06821694339
+:107B6000244A891808464C30FFF77CFA0446002C5A
+:107B700001D020469BE76D1C254800788542EDDB4F
+:107B80000020C0431E49088017490860C8640021CE
+:107B9000154841660020194908701449087000BF53
+:107BA00085E738B51048406E00283FD1142000907A
+:107BB0006A460D49503112480088AADF0446002C5D
+:107BC00001D0204638BD084800680749C8646846A7
+:107BD0000188054841660146096868225143054A03
+:107BE000891808464C3019E0FC24002000210020B0
+:107BF000F024002064250020FE200020FF2000202B
+:107C0000E2200020E0200020EC200020FC200020CA
+:107C1000E4200020F4200020FD2000201C22DF4969
+:107C2000FCF7B2FADD48FFF71DFACBE70820C9E7F9
+:107C300010B500BFDA480078002801D1082010BD37
+:107C400000BF0020D7490870D7490870D74908708D
+:107C5000FFF729FBF3E770B504460D46601C0DD015
+:107C6000D0480078844209DA002D07D068206043AC
+:107C7000CF49401840308078022801D1072070BDDC
+:107C800068206043CA4A80180146423107222846CC
+:107C900000F0E6FD0020F2E710B50246002107E003
+:107CA000C800C44BC01840688B00C34CE050491C4E
+:107CB000C24800788142F3DB002107E0C800C04BD6
+:107CC000C01840688B00BF4CE050491CBE4800788B
+:107CD0008142F3DBBC4800781071BB480078002873
+:107CE00001D0B84800E000201060B448007810735C
+:107CF000B2480078002801D0AF4800E00020906032
+:107D0000002010BDF8B506460F461446069D002C0F
+:107D100003D0AE48816820468847F8BD3EB5054689
+:107D2000A868002801D107203EBD502001900A20FC
+:107D30000290A7480090A749684600F026FB044639
+:107D4000002C01D02046EFE720200190A249684690
+:107D500000F01BFB0446002C01D02046E4E79B48C2
+:107D600029460EC90EC068218C484C3800F022FE0E
+:107D70000020C04389494C39086098490880002098
+:107D800088490870884908708849087000BF94497C
+:107D9000087000BF8D48807800281FD0FFF783FA55
+:107DA0000446002C01D02046BEE700207D49087023
+:107DB000087810E00022D243682141437C4B5A509E
+:107DC000002268214143C9184A66521E6821414376
+:107DD000C918CA64401C0728ECDB06E0FFF7E4FB87
+:107DE0000446002C01D020469EE701206C49087013
+:107DF000002099E710B5024600BF694800780028C6
+:107E000001D1082010BD00BF002901D10E20F9E7E3
+:107E10000888644B1B78984201DA0C20F2E761482D
+:107E200000780880002A01D10020EBE7002007E05D
+:107E3000682343435E4C1B19DB8F44001353401CE3
+:107E4000584B1B789842F3DB0020DBE7F8B5074678
+:107E500000BF53480078002801D10820F8BD00BFBA
+:107E60000026F64300240AE0682060435049401889
+:107E7000C08FB84201D166B205E0601CC4B2494867
+:107E800000788442F0DB00BF701C01D10520E5E7DB
+:107E9000F4B226E0621C68235A43454BD11868228D
+:107EA0006243D0186822FCF76FF9682060434049AC
+:107EB0000858401E682161433D4A50506820604385
+:107EC00011464018C06C411C09D06820604311461F
+:107ED0004018C06C401E682161438918C864601C4A
+:107EE000C4B230480078401EA042D3DC2D49097846
+:107EF000491E682251432E4A8818682100F05AFD15
+:107F000028480078401E27490870FFF7CCF905463D
+:107F1000002D01D02846A1E70020234908702349FD
+:107F2000087000240CE068216143214A8818FFF79B
+:107F300040F80546002D01D028468FE7601CC4B2EA
+:107F4000184800788442EEDB00240EE0682161438B
+:107F5000174A891808464C30FFF784F80546002D6B
+:107F600001D028467AE7601CC4B20E4800788442EB
+:107F7000ECDBFFF71FFA002070E7014600BF08485E
+:107F80000078002801D10820704700BF144800780D
+:107F9000C007C00F08700020F6E700004825002049
+:107FA000E0200020E4200020FC200020FD20002014
+:107FB0006425002074280020D4240020E62000201E
+:107FC0003C280020B8240020E5200020F0240020D8
+:107FD000057D0100EC200020F4200020E22000209C
+:107FE000FE20002000231C214143FA4A53541C2147
+:107FF000414389184B6002231C21414389188B603F
+:1080000000231C2141438918CB601C21414389185E
+:108010000B611C21414389184B611C214143891884
+:108020008B61704710B50020EA49091F087048703D
+:108030008870002403E02046FFF7D4FF641C1E2C48
+:10804000F9D310BDF8B50F25E248001F00781C21B8
+:108050004843E0494418E06800902078042810D193
+:108060008006006981B2009800F0A6FCA16946185C
+:10807000304650DF0546002D02D1A069401CA061AA
+:1080800026E02078022823D12769A0693F18A1693A
+:1080900060694118009808180090A1696068461A44
+:1080A00001208002864205D2B2083946009851DF8D
+:1080B000054605E0FF2201323946009851DF0546AA
+:1080C000002D04D101218902A0694018A06100BFE0
+:1080D000002D03D10120BF49091F88702846F8BD33
+:1080E000FFB581B005460E46174600240420B94965
+:1080F000091F8978002945D1B649091F4978002907
+:1081000040D1B44A121F1170114609781C22514304
+:10811000B04A5554111F09781C225143AD4A8918A1
+:108120000F61111F09781C225143AA4A89187368EC
+:108130003268CB608A60A74A121F12781C235A4308
+:10814000A44BD218049951601A1F12781C235A4369
+:10815000A04BD2180A99516100239E49091F097842
+:108160001C2251439B4A89188B61111F4978491C75
+:10817000121F5170FFF766FF002801D0112838D177
+:10818000002036E09349091F49781E2931D09149D2
+:10819000091F09788F4A121F52788918CCB21E2CF9
+:1081A00002DB21461E39CCB21C216143894A555459
+:1081B0001C21614389180F611C2161438918736870
+:1081C0003268CB608A601C226243824BD2180499C9
+:1081D00051601C226243D2180A99516100221C216D
+:1081E0006143C9188A61191F4978491C1A1F5170C7
+:1081F000002005B0F0BDF8B504467648001F0078B1
+:108200001C21484373490D5C081F00781C2148431A
+:1082100070494018806800016F490E58042D01D143
+:10822000002007E06B48001F00781C214843694983
+:10823000401840686749091F009009781C22514383
+:10824000644A89180B69111F09781C225143614A3D
+:1082500089180846083022462946B047F8BD70B54F
+:108260005C48001F00781C2148435A49451869683A
+:10827000A86988420FD35749091F0878FFF7B2FE53
+:108280005448001F4078401E5249091F4870084654
+:108290000078401C087000244E48001F40780028D9
+:1082A00009D0FFF7CFFE0446002C04D0112C02D0D9
+:1082B0002046FFF7A0FF204670BD70B5054600269A
+:1082C0004448001F8078012829D100204149091F16
+:1082D0008870022D02D0032D1ED119E03D48001FE9
+:1082E00000781C2148433B4944182078042803D1D6
+:1082F0006168A069884202D33046FFF77CFFFFF730
+:10830000AEFF0646002E02D03046FFF774FF04E0B1
+:108310000D20FFF770FF00E000BF00BF00BF70BD81
+:1083200010B5FFF77FFE00202C4908602C484069FB
+:10833000401C08D001221207126991B2284A5069E4
+:1083400000F03AFB02E0012000074069801E012195
+:108350000907096989B24843224908600120224976
+:10836000087000200DE0002102011B4B9950020112
+:10837000D21891600201D218D1810201D2189181E4
+:10838000401C0228EFD3002010BDF8B504460D466E
+:1083900000BF15480078002801D10820F8BD00BFB3
+:1083A000002C01D10E20F9E7002D01D10E20F5E7B8
+:1083B0002068002801D10E20F0E70121090760683C
+:1083C000096989B2884202D8606810280ED2072055
+:1083D000E4E70000E8280020302C002008210020DD
+:1083E000001000100C21002004210020A0680028AB
+:1083F0001CD0DD484069401C08D0012212071269D8
+:1084000091B2D94A506900F0D7FA02E00120000782
+:10841000406901210907096989B248436268A16876
+:108420005143D24A12688918884201D20720B5E721
+:10843000CF480068022801D10420AFE7CC4800688B
+:108440002860CA4800686860C94968680968090105
+:10845000C84A89184860C64920680968090150500F
+:10846000C349606809680901891888602089C04982
+:10847000096809018918888100276668A1684E4348
+:1084800000BF781C87B201200007006980B2B042AB
+:1084900005D201200007006980B2361A00E00026EC
+:1084A00001200007006980B2B04909684018AF494F
+:1084B000086001200007006980B2B042E1D9AC48F1
+:1084C00000680001AB494018C781A9480068401CFA
+:1084D000A7490860002061E73CB5034600BFA648F5
+:1084E0000078002801D108203CBD00BF002B01D13D
+:1084F0000E20F9E7002A01D10E20F5E718680228BE
+:1085000005D2186800019B4C2058002801D1072093
+:10851000EAE75C68186801940090019C1868000103
+:10852000944D40198068484320180190009800013C
+:108530002C4600198089009C24016419A4686043BA
+:10854000009C2401641964680019019CA04201D8B0
+:108550000720C9E7019C0098546010600020C3E721
+:10856000F8B504460D4616461F4600BF82480078FF
+:10857000002801D10820F8BD00BF002D01D10E2038
+:10858000F9E7002C01D10E20F5E72068022805D27A
+:108590002068000177490858002801D10720EAE740
+:1085A0002068000173494018808921680901714AD7
+:1085B0008918896848432168090189184968401861
+:1085C0006168884201D80720D5E7002E06D02068D0
+:1085D0000001684940188068B04201D20720CAE70C
+:1085E000F01921680901634A89188968884201D90C
+:1085F0000720C0E7284600F0AEF8002804D0E819AC
+:1086000000F0A9F8002801D11020B4E733462A462B
+:10861000214602200097FFF763FDACE7F8B5074657
+:108620000C4615461E4600BF53480078002801D16D
+:108630000820F8BD00BF002C01D10E20F9E7002F63
+:1086400001D10E20F5E72068022805D2206800013C
+:1086500048490858002801D10720EAE720680001AE
+:1086600044494018808921680901424A891889686B
+:10867000484321680901891849684018616888429F
+:1086800001D80720D5E7002D06D020680001394920
+:1086900040188068A84201D20720CAE7A8192168BB
+:1086A0000901344A89188968884201D90720C0E73E
+:1086B000384600F050F8002804D0B81900F04BF804
+:1086C000002801D11020B4E7606881192A46384695
+:1086D00000F0C6F80020ACE7F8B504460F4600BF2E
+:1086E00025480078002801D10820F8BD00BF002CE3
+:1086F00001D10E20F9E72068022805D22068000188
+:108700001C490858002801D10720EEE72068000125
+:1087100018494018808921680901164A8918896812
+:1087200048432168090189184968401861688842EE
+:1087300001D80720D9E7206800010E494018C689F2
+:10874000002033460246214600900420FFF7C8FC73
+:1087500005462846C9E701468807800F01D1012058
+:1087600070470020FCE70000001000100C210020E2
+:1087700008210020302C00200421002070B5034681
+:108780000C46002A01D10D4D00E015882846002135
+:1087900010E0051206023543A8B25D5C684005068C
+:1087A0002D0F684005034540A8B205062D0D6D004C
+:1087B0006840491CA142ECD370BD0000FFFF0000DF
+:1087C000034610B50B439B070FD1042A0DD308C8ED
+:1087D00010C9121FA342F8D018BA21BA884201D991
+:1087E000012010BD0020C04310BD002A03D0D307D4
+:1087F00003D0521C07E0002010BD03780C78401C09
+:10880000491C1B1B07D103780C78401C491C1B1BFF
+:1088100001D1921EF1D1184610BD000030B5441CA4
+:1088200003E00178401C00290DD08107F9D10B4BE2
+:10883000DD0104C8D11A91432940FAD0001B0A0671
+:1088400003D0C01E30BD001B30BD0A0401D0801E05
+:1088500030BD0902FCD0401E30BD00000101010105
+:10886000F8B5042A2CD3830712D00B78491C037067
+:10887000401C521E83070BD00B78491C0370401C10
+:10888000521E830704D00B78491C0370401C521EF3
+:108890008B079B0F05D0C91ADF002023DE1B08C9F8
+:1088A0000AE0FBF771FCF8BD1D4608C9FD401C46F7
+:1088B000B4402C4310C0121F042AF5D2F308C91A81
+:1088C000521EF0D40B78491C0370401C521EEAD48F
+:1088D0000B78491C0370401C521EE4D409780170C7
+:1088E000F8BD431A70B593421BD28918801804E072
+:1088F000491E521E0B78401E03709307F8D106E004
+:1089000010390B461038103A78CB78C01038102A3E
+:10891000F6D203E0001F091F0B680360121FF9D590
+:1089200070BDFBF731FC70BDF8B506461446084330
+:10893000800707D0751AA54219D20819102D13D235
+:10894000311907E03046FFF7CCFFF8BD401E491E45
+:1089500002780A70641EF9D2F8BD471B2A463946D0
+:10896000FFF77EFF641B3846A542F6D3011B224663
+:108970003046FFF775FFF8BD01E004C0091F042968
+:10898000FBD28B0701D50280801CC90700D0027082
+:10899000704700290BD0C30702D00270401C491E4B
+:1089A000022904D3830702D50280801C891EE3E7D5
+:1089B0000022EEE70022DFE7002203098B422CD3DE
+:1089C000030A8B4211D300239C464EE003460B431F
+:1089D0003CD4002243088B4231D303098B421CD381
+:1089E000030A8B4201D394463FE0C3098B4201D373
+:1089F000CB01C01A524183098B4201D38B01C01AAB
+:108A0000524143098B4201D34B01C01A5241030921
+:108A10008B4201D30B01C01A5241C3088B4201D3D0
+:108A2000CB00C01A524183088B4201D38B00C01A7D
+:108A3000524143088B4201D34B00C01A5241411AA4
+:108A400000D201465241104670475DE0CA0F00D087
+:108A50004942031000D34042534000229C46030980
+:108A60008B422DD3030A8B4212D3FC22890112BA06
+:108A7000030A8B420CD3890192118B4208D38901DE
+:108A800092118B4204D389013AD0921100E08909F6
+:108A9000C3098B4201D3CB01C01A524183098B42D7
+:108AA00001D38B01C01A524143098B4201D34B01C0
+:108AB000C01A524103098B4201D30B01C01A524123
+:108AC000C3088B4201D3CB00C01A524183088B42AA
+:108AD00001D38B00C01A5241D9D243088B4201D333
+:108AE0004B00C01A5241411A00D20146634652411E
+:108AF0005B10104601D34042002B00D5494270471D
+:108B000063465B1000D3404201B50020C046C0461A
+:108B100002BD704770477047754600F023F8AE46B7
+:108B2000050069465346C008C000854618B020B508
+:108B3000FBF756FB60BC00274908B6460026C0C5B7
+:108B4000C0C5C0C5C0C5C0C5C0C5C0C5C0C5403D05
+:108B500049008D4670470446C046C0462046FBF794
+:108B6000FCFA000000487047502C002001491820F2
+:108B7000ABBEFEE726000200704700000D180100A2
+:108B80000F1801000A180100A88B01000020002026
+:108B90001001000004410100B88C010010210020E8
+:108BA000A01B0000204101000024F400FFFF010091
+:108BB00000000000000000000000000000000000B5
+:108BC00000000000000000000000000000000000A5
+:108BD0000000000000000000000000000000000095
+:108BE0000000000000000000000000000000000085
+:108BF0000000000000000000000000000000000075
+:108C00000000000000000000000000000000000064
+:108C10000000000000000000000000000000000054
+:108C20000000000000000000000000000000000044
+:108C30000000000000000000000000000000000034
+:108C40000000000000000000000000000000000024
+:108C50000000000000000000000000000000000014
+:108C60000000000000000000000000000000000004
+:108C700000000000000000000000000000000000F4
+:108C800000000000000000000000000000000000E4
+:108C900000000000000000000000000000000000D4
+:108CA00000000000000000000000000000000000C4
+:088CB0000000000000000000BC
+:04000005000140C1F5
+:00000001FF
diff --git a/app/src/main/res/raw/ble_app_hrs_s110_v7_0_0.hex b/app/src/main/res/raw/ble_app_hrs_s110_v7_0_0.hex
new file mode 100644
index 000000000..b01a5eb63
--- /dev/null
+++ b/app/src/main/res/raw/ble_app_hrs_s110_v7_0_0.hex
@@ -0,0 +1,1376 @@
+:020000040001F9
+:10600000D8360020A1610100BB610100BD61010023
+:106010000000000000000000000000000000000080
+:10602000000000000000000000000000BF6101004F
+:106030000000000000000000C1610100C361010018
+:10604000C5610100C5610100C5610100C5610100B4
+:10605000C561010000000000C5610100C5610100CB
+:10606000C5610100C5610100C5610100C561010094
+:10607000C5610100C5610100C5610100C561010084
+:10608000C5610100E3870100C5610100C561010030
+:1060900005880100C56101007B8C0100C56101001C
+:1060A000C5610100C56101000000000000000000A2
+:1060B00000000000000000000000000000000000E0
+:1060C00000F002F800F031F80CA030C808382418AD
+:1060D0002D18A246671EAB4654465D46AC4201D120
+:1060E00000F023F87E460F3E0FCCB6460126334221
+:1060F00000D0FB1AA246AB4633431847C4530000F6
+:10610000D4530000103A02D378C878C1FAD85207A5
+:1061100001D330C830C101D504680C6070471FB589
+:10612000C046C0461FBD10B510BD05F045F911466B
+:10613000FFF7F5FF00F0F4FD05F05DF903B4FFF79C
+:10614000F2FF03BC05F062F9401E00BF00BF00BFB4
+:1061500000BF00BF00BF00BF00BF00BF00BF00BF47
+:1061600000BFF1D17047000070B505460C461646D9
+:1061700002E00FCC0FC5103E102EFAD2082E02D32B
+:1061800003CC03C5083E042E07D301CC01C5361F3E
+:1061900003E021782970641C6D1C761EF9D270BD55
+:1061A00003210C4802680A4302600B4802680A4354
+:1061B00002600A4880470A480047FEE7FEE7FEE71C
+:1061C000FEE7FEE7FEE7000006480749074A084BDE
+:1061D000704700002405004054050040996201000A
+:1061E000C1600100D8260020D8360020D82E00201B
+:1061F000D82E002032483349086070473248008C5E
+:10620000C0B2012812D13048808C0007000F00284E
+:106210000CD12D48806AF0210840402806D12A4838
+:10622000C06A0840002801D1012070470020FCE727
+:106230002548008CC0B201282CD12348808C00074F
+:10624000000F002826D12048806AF021084000284D
+:1062500006D11D48C06A0840002801D101207047BE
+:106260001948806AF0210840102806D11648C06AF3
+:106270000840002801D10120F1E71348806AF0218D
+:106280000840302806D11048C06A0840002801D1D3
+:106290000120E4E70020E2E710B5FFF7C9FF00287E
+:1062A00005D00A480A494860C8130A498861FFF7BF
+:1062B000A5FF002802D001200749886010BD00001A
+:1062C0000024F40000200020C00F00F0DFFF07C012
+:1062D00000050040006C00400006004030B513048B
+:1062E0008C0023430524240707252D02641985000B
+:1062F000635130BD0F2000F038FD00BFBFF34F8F5A
+:10630000FD48FE49C860BFF34F8F00BF00BFFEE7E6
+:1063100004460D462A462146F948FFF7EBFF70B5C3
+:10632000F849F94801F04DFEC5B22946F74801F099
+:106330008AF80446002C0DD0082C0BD0F448844277
+:1063400008D0F448844205D000BFF3A2BC21204607
+:10635000FFF7D0FF70BD10B50446FFF7E0FF10BD9A
+:1063600070B50646EF49F04801F02BFE85B2EF48C4
+:106370000068401CED4908602946ED4801F052FADA
+:106380000446002C0DD0082C0BD0E148844208D0E4
+:10639000E048844205D000BFDFA2E9212046FFF794
+:1063A000A9FF0321E148006804F056FF002901D04D
+:1063B000012000E00020DF49087070BD70B505467F
+:1063C000DC480078002809D0DB49DC4801F0F9FD01
+:1063D00084B22146D64801F054FA00BF70BD10B512
+:1063E0000446D748007801214840D549087008463E
+:1063F0000178CF4801F076FA10BD10B5082000F002
+:10640000ABFC092000F0A8FC0F2000F0A5FC10BD9B
+:10641000F8B500BF0020CB4B05221146009002F0DA
+:10642000F5F9054600BF2E46002E06D000BFBAA2E1
+:10643000FF2131313046FFF75DFF00BF00BFC24A88
+:106440000121C24802F05FFA044600BF2546002D34
+:1064500006D000BFB0A2FF2137312846FFF74AFF20
+:1064600000BFBB4A0121BB4802F04DFA044600BF01
+:106470002546002D06D000BFA7A2FF213C312846AB
+:10648000FFF738FF00BFB44A0121B44802F03BFADD
+:10649000044600BF2546002D06D000BF9EA2FF2166
+:1064A00041312846FFF726FF00BFAD4A0121AD4824
+:1064B00002F029FA044600BF2546002D06D000BF91
+:1064C00095A2FF2146312846FFF714FF00BFF8BD13
+:1064D0003EB500BF6846007801090901491C0091DA
+:1064E00068460078F02188431030009000BF0A22EF
+:1064F0009DA168467CDF044600BF2546002D06D0DE
+:1065000000BF85A2FF215A312846FFF7F3FE00BFE6
+:10651000984878DF044600BF2546002D06D000BF0E
+:106520007DA2FF215D312846FFF7E4FE00BF002079
+:1065300001900290FF219131684681804900C1801D
+:1065400000210181FF219131418101A87ADF0446B8
+:1065500000BF2546002D06D000BF6FA2FF21673186
+:106560002846FFF7C7FE00BF3EBD30B593B00621F9
+:106570000491814A07CA01AB07C3382105A804F07A
+:1065800069FE02216846017501218175018304A914
+:1065900007910321818501A90C91002105A801F033
+:1065A00077FC044600BF2546002D06D000BF5AA246
+:1065B000FF2188312846FFF79DFE00BF14216F4858
+:1065C00004F048FE00206D490870486008722820D9
+:1065D0000882B420488213B030BD30B59BB003218F
+:1065E0000091142116A804F035FE00201690012118
+:1065F00010A801776846189000BF18A80079010913
+:106600000901491C18A801710079F0218843103054
+:1066100018A9087100BF00BF16A8407B000900013F
+:1066200016A9487316A8407BF021884316A9487321
+:1066300000BF00BF16A8807B00090001887316A860
+:10664000807BF021884316A9887300BF00BF18A87B
+:10665000C07901090901491C18A8C171C079F0214C
+:106660008843103018A9C87100BF00BF16A8007C6D
+:106670000009000116A9087416A8007CF0218843BF
+:1066800016A9087400BF2A4801F096F8044600BF16
+:106690002546002D06D000BF1FA2FF21B33128469A
+:1066A000FFF728FE00BF142111A804F0D3FD00BF9E
+:1066B00010A8407C01090901491C10A84174407CC4
+:1066C000F0218843103010A9487400BF00BF11A802
+:1066D000807B00090001401C11A9887311A8807BF0
+:1066E000F0218843103011A9887300BF00BF11A8A2
+:1066F000C07B0009000143E00400FA0500ED00E062
+:10670000EFBEADDE7C2100200820002014210020F7
+:1067100004300000013400002E2E5C6D61696E2E85
+:10672000630000008C210020102000203420002075
+:106730002C210020062000209C2100201820002071
+:1067400038200020AC2100205763010020200020C9
+:106750006163010024200020BD6301002820002087
+:10676000DF6301002C2000204E6F726469635F4874
+:10677000524D00004103000038B401000021002008
+:10678000C87311A8C07BF021884311A9C87300BF4A
+:1067900000BF10A8007D01090901491C10A801755E
+:1067A000007DF0218843103010A9087500BF00203B
+:1067B0001190012110A8017200201390642110A8EB
+:1067C000017411A9F64800F01DFE044600BF2546DD
+:1067D000002D06D000BFF34AFF21C5312846FFF740
+:1067E00089FD00BF402101A804F034FDEEA101A8FD
+:1067F00001F0B4FB00BF10A8007801090901491C91
+:1068000010A801700078F0218843103010A908709A
+:1068100000BF00BF10A8407800090001487010A810
+:106820004078F021884310A9487000BF01A800F00B
+:10683000C1FB044600BF2546002D06D000BFD94A43
+:10684000FF21D0312846FFF755FD00BF1BB030BDFA
+:1068500010B55120D9490860642048600120886043
+:106860000873D74801F0A0FB8C20D6490860FF20B0
+:106870002D3048600A20886000200873D24801F05B
+:1068800093FB6420D1490860FF20F5304860012067
+:10689000886000200873CE4801F086FB10BD70B5FB
+:1068A000002201210904CB48006802F073F8044675
+:1068B00000BF2546002D06D000BFBA4AFF21F731A0
+:1068C0002846FFF717FD00BF00220121C903C24877
+:1068D000006802F05FF8044600BF2546002D06D090
+:1068E00000BFB04AFF21FA312846FFF703FD00BF81
+:1068F0000022BA49BA48006802F04CF8044600BFCA
+:106900002546002D06D000BFA64AFF21FD312846AE
+:10691000FFF7F0FC00BF00220521C903B148006861
+:1069200002F038F8044600BF2546002D05D000BF10
+:106930009C4AAD492846FFF7DDFC00BF70BD38B565
+:10694000684603F090F9044600BF2546002D06D0A6
+:1069500000BF944AA4490E312846FFF7CBFC00BF84
+:106960000098002803D00120A049087038BDA04835
+:1069700073DF044600BF2546002D06D000BF894ABC
+:10698000994917312846FFF7B5FC00BF082000F0F1
+:10699000ECF900BFEAE770B505462878002810D169
+:1069A0003B219448008876DF044600BF2646002E2F
+:1069B00006D000BF7B4A8C492E313046FFF79AFC47
+:1069C00000BF70BD044600BF764A4721C90020467B
+:1069D000FFF790FC30B587B01C21684604F03AFC04
+:1069E000002000900520C00301900F2000040290B9
+:1069F000032168460173804881896846C18100216E
+:106A000001747E4805907E480690684602F0CDF9F4
+:106A1000044600BF2546002D06D000BF614A7249DA
+:106A200050312846FFF766FC00BF07B030BD70B597
+:106A300004462088102804D011280CD0192825D10C
+:106A40000FE0092000F091F9082000F093F9A088E8
+:106A5000684908801BE0092000F08CF9FFF76FFF00
+:106A600015E0A079002810D1082000F083F934DF68
+:106A7000054600BF2E46002E06D000BF494A5A499F
+:106A800073313046FFF736FC00BF00E000BF00BFA7
+:106A900070BD10B50446022C02D0032C0BD100E0CF
+:106AA00000BF52480078002804D000204F490870E9
+:106AB000FFF745FF00E000BF00BF10BD10B5044662
+:106AC000204603F0ACFF21464B4800F032FD214642
+:106AD000334800F09AFB204602F03BFA2046FFF7CD
+:106AE000A6FF10BD10B50446204602F028FD204642
+:106AF000FFF7CFFF10BDF8B500BF0023502241497A
+:106B0000082002F072F8054600BF2E46002E06D07F
+:106B100000BF244A3449BB313046FFF7EBFB00BFCE
+:106B200000BF002000900090684660DF044600BF70
+:106B30002546002D06D000BF1A4A2B49C3312846EE
+:106B4000FFF7D8FB00BF304802F085F8044600BFCD
+:106B50002546002D06D000BF124A2349C8312846D9
+:106B6000FFF7C8FB00BF294802F07EF8044600BFCB
+:106B70002546002D06D000BF0A4A1B49CC312846C5
+:106B8000FFF7B8FB00BFF8BD10B50322002108468F
+:106B9000FFF7A4FB032200210120FFF79FFB10BD9C
+:106BA00014210020186701004E6F726469635365F9
+:106BB0006D69636F6E647563746F72007C21002071
+:106BC000082000208C210020102000209C21002083
+:106BD000182000202020002024200020662600000D
+:106BE000282000202C200020FF0100003120002060
+:106BF00000210020042000202C21002097690100A2
+:106C0000C56901003C240020BD6A0100E56A01005D
+:106C1000F8B506460F46144600BF2546002D05D0A0
+:106C200000BF574A57492846FFF764FB00BF0020C2
+:106C3000F8BD7FB502F026FD044600BF2546002DB5
+:106C400006D000BF4E4A4F490F312846FFF752FB8E
+:106C500000BF01204C490969C1400140002900D111
+:106C600000E00020039003A803F076FA044600BF7A
+:106C70002546002D06D000BF414A4249153128461D
+:106C8000FFF738FB00BF062101A8023004F0E0FA4C
+:106C90001E216846C180007A40084000401C6946B9
+:106CA00008726846007A022188436946087268467D
+:106CB000007A1C2188430C30694608726846007AC5
+:106CC00020218843694608720721684641721021D5
+:106CD00081722E48009001216846017169462C4856
+:106CE00003F0A9FA044600BF2546002D06D000BFD8
+:106CF000234A244924312846FFF7FCFA00BF7FBD10
+:106D000070B540DF044600BF2546002D06D000BF09
+:106D10001B4A1C492D312846FFF7ECFA00BF70BD15
+:106D2000FFF76BFBFFF730FFFFF7E5FEFFF770FBA8
+:106D3000FFF77FFFFFF7CCFBFFF717FCFFF74DFCDA
+:106D4000FFF786FDFFF746FEFFF7A9FDFFF7F7FD0A
+:106D500000BFFFF7D5FFFCE7032105221207072339
+:106D60001B02D2188300D150704701218140064A8E
+:106D70009160704701218140034AD1607047000053
+:106D800018670100E602000000050050116C0100C8
+:106D90003020002070B504460D4600BF002D01D004
+:106DA000012000E000200646002E05D100BFE6A22B
+:106DB0003321FFF79FFA00BF00BF00BF002C01D0B6
+:106DC000012000E000200646002E05D100BFDEA213
+:106DD0003421FFF78FFA00BF00BF28782070298880
+:106DE0002879FF231B02194000200206080A1043DD
+:106DF000607028791B022968194000200204080CE1
+:106E00001043A07029791B0228681840002109024C
+:106E1000000E0843E07028792071287A6071190CFF
+:106E2000A8680840000AA0710902A8680840000C80
+:106E3000E07170BDF8B505460E46002400BF002E77
+:106E400001D0012000E000200746002F05D100BF3F
+:106E5000BDA24B21FFF74EFA00BF00BF00BF002DBF
+:106E600001D0012000E000200746002F05D100BF1F
+:106E7000B5A24C21FFF73EFA00BF00BF3278204692
+:106E8000611CCCB22A542919708800F054F90019F9
+:106E9000C4B22919B08800F04EF90019C4B22919FA
+:106EA000F08800F048F90019C4B200BF072C01D1E6
+:106EB000012000E000200746002F05D100BFA2A25C
+:106EC0005421FFF717FA00BF00BFF8BDF0B58FB02F
+:106ED00007460D4614461E4600BF002D01D0012076
+:106EE00000E0002000900098002805D100BF96A285
+:106EF0006D21FFF7FFF900BF00BF00BF002C01DDCF
+:106F0000012000E0002000900098002805D100BF7B
+:106F10008DA26E21FFF7EEF900BF00BF1C2107A86C
+:106F200004F098F96946087F02218843801C69466D
+:106F30000877002008900A900B900C900D9000BFED
+:106F4000012108A881766846078700BF00200190CC
+:106F500031786846017171784171807906218843E2
+:106F6000811C684681718079082188430146684602
+:106F7000817180791021884301466846817180794A
+:106F80004108490068468171142102A804F062F9A1
+:106F90000EA8029001A80390684604820021418255
+:106FA0008482069502AA07A974480088149BA2DF70
+:106FB0000FB0F0BD7FB5044600BF01216846817364
+:106FC0006F49818100BF6D4A03A90120A0DF0546FA
+:106FD000002D02D0284604B070BD208800280DDDA9
+:106FE00068480090228823463C3367486168FFF771
+:106FF0006DFF0546002D01D02846ECE720890028CA
+:107000000EDD62480090228923463C335E48401FD3
+:10701000E168FFF75BFF0546002D01D02846DAE75F
+:10702000208A00280EDD5A480090228A23463C33ED
+:107030005548001F6169FFF749FF0546002D01D043
+:107040002846C8E7208B00280EDD52480090228B8E
+:1070500023463C334C48801EE169FFF737FF054665
+:10706000002D01D02846B6E7208C00280EDD4A48C6
+:107070000090228C23463C334348C01E616AFFF7D0
+:1070800025FF0546002D01D02846A4E7208D0028C5
+:107090000EDD42480090228D23463C333A48401E84
+:1070A000E16AFFF713FF0546002D01D0284692E75D
+:1070B000206B002813D001A8216BFFF76BFE384826
+:1070C00023463C33082201A900902F48801FFFF778
+:1070D000FDFE0546002D01D028467CE700BF606B11
+:1070E00000280FD02F480090606B027923463C3374
+:1070F00001682548401CFFF7E9FE0546002D01D038
+:10710000284668E7A06B002813D001A8A16BFFF701
+:1071100091FE254823463C33072201A900901A48D6
+:107120002730FFF7D3FE0546002D01D0284652E751
+:1071300000BF00204FE702460A70FF200002104007
+:1071400000124870022070472E2E5C2E2E5C2E2ED0
+:107150005C2E2E5C2E2E5C536F757263655C626CC8
+:10716000655C626C655F73657276696365735C62AA
+:107170006C655F6469732E63000000003A20002094
+:107180000A1800003C200020292A0000442000208A
+:107190004C200020542000205C200020642000208F
+:1071A0006C200020742000207C2000208A884282ED
+:1071B00070470022D24342827047F8B504460E461B
+:1071C000207D002820D0B51D2988608981421AD1F0
+:1071D000288B022817D12068002814D000BF00BFD8
+:1071E000A97EEA7E120211430846C107C90F002991
+:1071F00002D00020009001E0012000906946204666
+:107200002268904700BF00BFF8BD70B505460C4628
+:107210002088102804D0112807D050280FD109E069
+:1072200021462846FFF7C2FF0AE021462846FFF71D
+:10723000C0FF05E021462846FFF7BFFF00E000BF82
+:1072400000BF70BDF0B591B004460D46207D00280A
+:1072500019D00020099000BF08A800790109090190
+:10726000491C08A801710079F0218843103008A951
+:10727000087100BF697B08A84171807906218843A5
+:10728000801C08A988711C210AA803F0E3FF08A844
+:10729000007A02218843811C08A80172007A10211B
+:1072A0008843217D002901D0012100E0002109014E
+:1072B00010221140084308A9087200200B900D907D
+:1072C0000E90207D002801D009A800E000200F903A
+:1072D0000020109000BF01216846817377498181A9
+:1072E00000BF00200290A97B68460172E97B4172D1
+:1072F000807A06218843811C68468172807A082141
+:107300008843014668468172807A1021884301468D
+:1073100068468172807A4108490068468172297BFB
+:107320000191142104A803F095FF03A8049002A87A
+:107330000590012168460183002141830121818359
+:1073400001A80890A088A31D04AA0AA9A2DF0646E6
+:10735000002E02D0304611B0F0BDA86800284DD0F4
+:1073600000BF0121684681735549818100BF00201B
+:107370000290287C6946087200BF6846407A01097D
+:10738000090168464172407AF02188436946487293
+:1073900000BF6846807A06218843811C6846817256
+:1073A000807A08218843014668468172807A1021DC
+:1073B0008843014668468172807A410849006846E0
+:1073C0008172A96800F0ACFD0746142104A803F0FF
+:1073D00041FF03A8049002A8059068460783002196
+:1073E0004183018B81830890E08822460E3204A9F4
+:1073F000A3DF0646002E03D03046ACE70020E08134
+:107400000020A8E7F8B504460D4628682060002053
+:10741000C043608228792075FF20207400BF0121BD
+:10742000684681702749018000BF221D69460120FE
+:10743000A0DF0646002E01D03046F8BD2946204682
+:10744000FFF700FFF9E733B585B004460025207C3F
+:107450006946097E88422FD0012104916846007E4A
+:107460002074E08806AB04AA0021A4DF0546002DA5
+:1074700002D0284607B030BD608A1349884219D02F
+:10748000207D002816D000200090019002900390EB
+:1074900001210491E1886846018001218170002169
+:1074A000818004A8029006A80390608A6946A6DF3E
+:1074B000054600E0082500BF2846DBE7192A000042
+:1074C000082900000F180000FFFF00008A880284CE
+:1074D00070470022D2430284704738B505460C46F7
+:1074E000208B022817D12868002814D000BF00BFC5
+:1074F000A17EE27E120211430846C107C90F00298E
+:1075000002D00020009001E001200090694628464A
+:107510002A68904700BF38BD70B504460D46AE1DC1
+:107520003188A089814203D131462046FFF7D5FF3B
+:1075300070BD70B505460C462088102804D011286F
+:1075400007D050280FD109E021462846FFF7BEFF9B
+:107550000AE021462846FFF7BCFF05E02146284601
+:10756000FFF7DAFF00E000BF00BF70BDF7B582B0E3
+:10757000044617460020019001256079002803D0B9
+:1075800004210198084301902220005D002803D0C7
+:1075900002210198084301900398FF280ADD012188
+:1075A0000198084301907919039800F0AAF940194D
+:1075B000C5B205E00398C2B22846691CCDB23A5460
+:1075C0004C20005B002803DD102101980843019046
+:1075D000002618E0A81C14280BD94C20005B801B47
+:1075E0004200700023462433C118184603F0ECFD16
+:1075F0000DE07919720023462433985A00F081F97E
+:107600004019C5B2761C4C20005BB042E2DC00BFE2
+:107610004C20005B801B81B24C20015301983870D4
+:10762000284605B0F0BD30B595B005460C460020A3
+:107630000D9000BF08A8007D01090901491C08A898
+:107640000175007DF0218843103008A9087500BF3E
+:10765000217B08A84175807D06218843811C08A8EC
+:1076600081751C210EA803F0F5FD08A8007E1021ED
+:107670008843103008A9087600200F9011901290CE
+:107680000DA813900020149000BF01216846817757
+:10769000A049818300BF00200690617B6846017687
+:1076A000A17B4176807E06218843811C68468176D5
+:1076B000807E08218843014668468176807E1021BD
+:1076C0008843014668468176807E41084900491C0E
+:1076D00068468176142108A803F0BCFD07A808902D
+:1076E00006A8099001AA00212846FFF73FFF01469E
+:1076F00068460185002141851421818501A80C90EF
+:10770000E8882B46083308AA0EA9A2DF15B030BDC1
+:1077100030B58FB005460C461C2108A803F09AFD31
+:1077200008A9087802218843801C08A90870002055
+:1077300009900B900C900D900E9000BF01216846AF
+:1077400081727449491C018100BF00200190E17BD6
+:1077500068460171217C4171807906218843811C32
+:1077600068468171807908218843014668468171A5
+:1077700080791021884301466846817180794108EB
+:10778000490068468171142103A803F063FD02A833
+:10779000039001A804900121684681820021C182E2
+:1077A00001210183A0680790E8882B46103303AAC3
+:1077B00008A9A2DF0FB030BDF8B504460E46306808
+:1077C0002060307960710020C04320840021222095
+:1077D00001554C20015300BF0121684681704E497C
+:1077E000018000BFA21D69460120A0DF0546002DD3
+:1077F00001D02846F8BD31462046FFF714FF054664
+:10780000002D01D02846F5E7B068002808D03146A1
+:107810002046FFF77DFF0546002D01D02846E9E709
+:107820000020E7E7F0B58BB004460F46208C3B49BB
+:10783000884222D006AA39462046FFF797FE064620
+:1078400005960020019002900390049021896846DB
+:107850008180012181710021018105A8039006A882
+:107860000490208C01A9A6DF0546002D04D16846AE
+:10787000808AB04200D00C2500E0082528460BB0D5
+:10788000F0BD70B504460D464C20005B14280AD1AB
+:107890002622A118881E03F097FC4C20005B401E96
+:1078A00081B24C2001534C20015B005B401C82B232
+:1078B0004C200253490020462430455270BD0146F9
+:1078C0004C20405A142801D1012070470020FCE7C9
+:1078D0000246108C114B984202D151710020704722
+:1078E0000820FCE722221154704713B582B00446E9
+:1078F00001200190208A03AB01AA0021A4DF04B07B
+:1079000010BD02460A70FF200002104000124870AD
+:1079100002207047372A00000D180000FFFF00000A
+:10792000F7B584B004460F4606980678B01C1F28A9
+:1079300003DC6078801C1F2802DD0C2007B0F0BD3E
+:107940001F20801B801E85B20295B11C781802A9E9
+:107950007DDF03900398002801D00398EEE720789C
+:10796000022808D168460089A84204DC0920019059
+:10797000684605890BE0082001906078002804D053
+:107980006078A84201DC657801E068460589681CDA
+:10799000C2B23046711CCEB23A543046721CD6B2D6
+:1079A0000199395406980178A81C0818C1B20698A4
+:1079B00001700020C2E7F8B505460C462078001D8E
+:1079C0001F2801DD0C20F8BD684679DF0646002E31
+:1079D00001D03046F7E703212278501C2070A954CB
+:1079E00019212278501C2070A9542078411968462A
+:1079F000008800F08AFA2178401820700020E2E721
+:107A0000F8B505460F4616461C466868002801D1A1
+:107A10000720F8BD2078801C298840181F2801DD28
+:107A20000C20F6E72878401CC1B22278501C207048
+:107A3000B1542178481C207077542A8823789819EB
+:107A4000696803F05DFB20782978401820700020D9
+:107A5000DFE730B503461078C01C1F2801DD0C207D
+:107A600030BD02241578681C10704C550A24157816
+:107A7000681C10704C551578681C10704B55002010
+:107A8000EEE7FFB587B006461D46109C0020069025
+:107A900020780590002747E0B900706841180A88EF
+:107AA0006846028149884181002203A902A865DF56
+:107AB00004900498002802D004980BB0F0BD6846EA
+:107AC000017B099881422ED10698002801D0002020
+:107AD00000E00220019020786946097B4118019856
+:107AE00008181F2801DD0C20E7E70698002809D1B7
+:107AF0002078401C20702278511C08982170A854CE
+:107B0000012006902078421903A902A865DF04909D
+:107B10000498002801D00498CFE720786946097BB3
+:107B20004018207000BF7F1C3088B842B4DC069833
+:107B3000002806D021780598401C081AC1B2059883
+:107B400029540020B9E7FFB581B0074615460A9EC3
+:107B5000009602223846049B0299FFF792FF0446E2
+:107B6000002C02D0204605B0F0BD00961022294618
+:107B70003846049BFFF785FF0446002C01D02046C1
+:107B8000F1E70020EFE701460888062808DB0888AF
+:107B90001922D201904205DD0888E04A904201D0C6
+:107BA000072070474888062808DB48881922D20138
+:107BB000904205DD4888D94A904201D00720F0E77D
+:107BC0000888D64A904208D04888904205D0088854
+:107BD0004A88904201DD0720E3E70020E1E7F8B59D
+:107BE00006460D4614462078801D1F2801D90C201A
+:107BF000F8BD3046FFF7C7FF0746002F01D03846D3
+:107C0000F6E705212278501C2070A9541221227811
+:107C1000501C2070A95422785119308800F075F951
+:107C200021784018207022785119708800F06DF981
+:107C30002178401820700020DAE7F8B505460E4696
+:107C400014462879801CC7B22078801CC0191F28D0
+:107C500001DD0C20F8BD781CC1B22278501C2070C8
+:107C6000B154FF212278501C2070B1542278911910
+:107C7000288800F04AF9217840182070A888002848
+:107C80000EDDA868002801D10720E3E7AA88237841
+:107C90009819A96803F034FA2078297940182070DF
+:107CA0000020D7E7FEB507460E461446386B00287D
+:107CB00001D10720FEBD0020019032E0396B0C227B
+:107CC000019850430D182879801CC0B2009000988C
+:107CD000401CC1B22278501C2070B1541621227869
+:107CE000501C2070B15422789119288800F00DF9A9
+:107CF000217840182070A88800280EDDA868002888
+:107D000001D10720D6E7AA8823789819A96803F03B
+:107D1000F7F920782979401820700198401CC0B2EA
+:107D200001903420C15D01988142C7DC0020C1E789
+:107D3000F8B504460F46164600250020307020781E
+:107D4000002809D0324639462046FFF7E9FD0546AE
+:107D5000002D01D02846F8BDA078002808D0314673
+:107D60003846FFF728FE0546002D01D02846F2E7E9
+:107D7000A08800280ADD33463A460121201DFFF77E
+:107D80003FFE0546002D01D02846E4E7E0680028C4
+:107D90000BD00020E168085632463946FFF759FEFD
+:107DA0000546002D01D02846D5E7208A00280CDDA5
+:107DB0003B4606220221204610300096FFF7C3FE04
+:107DC0000546002D01D02846C5E7208B00280CDD94
+:107DD0003B4607220321204618300096FFF7B3FEEA
+:107DE0000546002D01D02846B5E7208C00280CDD83
+:107DF0003B4615221421204620300096FFF7A3FEB3
+:107E00000546002D01D02846A5E7A06A002809D024
+:107E100032463946A06AFFF7E2FE0546002D01D042
+:107E2000284698E7E06A002809D032463946E06AD9
+:107E3000FFF703FF0546002D01D028468BE73420CD
+:107E4000005D002809DD324639462046FFF72AFF4B
+:107E50000546002D01D028467DE728467BE70146F0
+:107E60008888002808D08868002805D088680078AD
+:107E700004221040002801D1072070470020FCE7B1
+:107E800001468888002801DD072070470020FCE7B4
+:107E9000F3B593B00D46002112911191139800286B
+:107EA00014D01398FFF7DBFF0446002C02D02046C5
+:107EB00015B0F0BD12AA09A91398FFF739FF0446BF
+:107EC000002C01D02046F3E709AE00E00026002D8B
+:107ED00013D02846FFF7D4FF0446002C01D02046DB
+:107EE000E6E711AA01A92846FFF722FF0446002C65
+:107EF00001D02046DCE701AF00E0002710A803799D
+:107F00003A46017A304672DFD2E702460A70FF2015
+:107F1000000210400012487002207047FFFF00006E
+:107F2000F8B505460E46002432782146641C6A5492
+:107F300072782146641C6A5400BF022C01D10120D2
+:107F400000E000200746002F05D100BF08A2202135
+:107F5000FEF7D0F900BF00BF2046F8BD70B504465B
+:107F60000D46284603F0AAF82080656070BD000029
+:107F70002E2E5C2E2E5C2E2E5C2E2E5C2E2E5C5316
+:107F80006F757263655C626C655C626C655F73657E
+:107F90007276696365735C626C655F7372765F634A
+:107FA0006F6D6D6F6E2E63000A7B002A04D04A68E5
+:107FB00002600022027103E00A680260012202717D
+:107FC00070470246107900280FD048681368C01A1D
+:107FD0008B68984204D910688B68C018106013E051
+:107FE00048681060002010710EE010680B68C01A1D
+:107FF0008B68984204D910688B68C01A106003E03F
+:1080000008681060012010711068704710B50028D2
+:1080100019DAFB4A03071B0F083B9B089B00D25849
+:108020008307DC0EFF23A3409A438B071B0E8407B4
+:10803000E40EA3401A43F24B0407240F083CA408A3
+:10804000A4001A5118E0EF4A03231B02D218830838
+:108050009B00D2588307DC0EFF23A3409A438B0773
+:108060001B0E8407E40EA3401A43E64B03242402AC
+:108070001B198408A4001A5110BD10B50446E2482B
+:10808000846003211120FFF7C1FF10BD10B501204E
+:108090000004DE494860DD4940394860112000F0A5
+:1080A000F5FC112000F0E4FC0120D94908602F20E4
+:1080B000FEF74AF80120D749087010BD10B511200D
+:1080C00001218140CF4A8032116000BF01200004AD
+:1080D000CE498860CD49403988600120CC4948604C
+:1080E0002F20FEF731F80120C94988600020CA49D5
+:1080F00008602F20FEF728F80020C649087010BD40
+:10810000F0B5024624205043C44B1B68C118C44834
+:108110000068401C002802D1C148026059E0C048F4
+:108120004B68006824246043BC4C246800194068F4
+:10813000834217D8BA48006824235843B74B1B68BA
+:10814000C01840684B68C31AB548006824246043CF
+:10815000B24C246800194360B14800680862B04816
+:10816000026036E04B68AE4D2C6828680DE024258F
+:108170004543AA4E3668AD196D685B1B044624253D
+:108180004543A64E3668AD19286A451C002D07D018
+:1081900024254543A14E3668AD196D689D42E6D34E
+:1081A000451C002D0CD0242545439C4E3668AD1946
+:1081B0006D68EE1A24254543984F3F68ED196E60AF
+:1081C0004B60086224256543944E3668AD192A62D7
+:1081D00000BFF0BDF8B50646914805682C4609E099
+:1081E000B44200D109E02546242060438B49096848
+:1081F0004018046A601C0028F2D100BF601C0028EF
+:1082000000D1F8BDA54210D185480068242148431B
+:10821000824909684018006A814908600846006878
+:10822000401C002801D1FFF749FF242060437B490F
+:108230000968401847682420604378490968401855
+:10824000016A24206843754A12688018016224205C
+:108250006843724909684018046A601C00280CD001
+:10826000242060436D49096840184068C1192420E2
+:1082700060436A4A12688018416000BFC1E710B5C8
+:10828000112000F0FCFB10BD10B5142000F0F7FB2E
+:1082900010BD70B5044663480068002813D0614AD9
+:1082A000E169A06912689047054600BF2E46002E7E
+:1082B00007D000BF5CA2FF2164313046FEF71AF8F8
+:1082C00000BF00BF02E0A169E069884770BDF8B552
+:1082D00053480068401C00283AD0002700F008FCF2
+:1082E00004464D480168204600F005FC06464C480F
+:1082F000056811E02420684348490968441860680B
+:10830000B04200D90BE06068361A6068C719256A68
+:108310002046FFF7BEFF00BF681C0028EAD100BF5F
+:108320004D4800784D490978884209D14B4800787A
+:10833000401CC0B249490870022801D100200870D1
+:1083400046480078800046490F50FFF79DFF00BF68
+:10835000F8BD014640480078404A127890421DD04E
+:108360003D480078401C3C4A1070104600780228B6
+:1083700001D100201070384800788000384A105829
+:108380000860254A086812688018234A1060104661
+:1083900000680002000A1060012070470020086099
+:1083A00000BFFAE7FEB51E48006801902D4800782E
+:1083B000009076E00098C0002B49096844182578A1
+:1083C0006BE01820684361680F18681CC5B2A0787C
+:1083D000A84200D100253878022802D0032859D1BC
+:1083E00044E02421786848430C4909684618307DE8
+:1083F000002804D07868FFF7EDFE002030754AE0D1
+:108400001CED00E000E100E00015014040130140D8
+:1084100000100140AC20002098200020882000207F
+:1084200094200020A82000202E2E5C2E2E5C2E2EC4
+:108430005C2E2E5C2E2E5C536F757263655C6170D2
+:10844000705F636F6D6D6F6E5C6170705F74696D8E
+:1084500065722E6300000000A4200020A5200020EB
+:108460009C2000208C200020902000200CE0F84967
+:10847000096824225143F74A1268881800210175BF
+:10848000F34A016A116000BFF1480068401C0028EF
+:10849000EDD100E000BF00BF00BF6078A84290D1DE
+:1084A00000BF0098411EC9B20091002882D1E8485F
+:1084B00001680198814201D00120FEBD0020FCE747
+:1084C000F0B50346002426E0E14E366824277E43BB
+:1084D000E04F3F68F01946689E4203D94668F61A95
+:1084E00046601DE046689B1B4668341900264660BE
+:1084F0000675D74E3568D64F066A3E600669002E6F
+:1085000008D00E193602360A86600669C6601668FB
+:108510000662156000BFCE480068401C0028D3D119
+:1085200000BFF0BDFEB50746C94800680290CA48C2
+:10853000007801906BE00198C000C84909684518AF
+:108540005DE0781C002807D03E4624207043C149D6
+:1085500009684418276A23E02978182359436A6870
+:1085600050182978491C29702978AA78914201D19C
+:1085700000212970466824217143B64A12688C187C
+:108580000178012902D1217D002900D037E08168DE
+:10859000A160C168E160016921614169E16100BFD9
+:1085A000AF49A0680968401A0002000AAD49884234
+:1085B00008D2AB49A068096800F09DFAE16840184C
+:1085C000606011E0A648A168006800F094FA00908D
+:1085D000E1680098814204D9E1680098081A606057
+:1085E00001E00020606000BF0020A060E06001208A
+:1085F0002075801E20623046FFF782FD00BF781C88
+:1086000000289ED12878697888429AD100BF0198C5
+:10861000411EC9B2019100288DD18D480168029890
+:10862000814201D00120FEBD0020FCE7FEB5884854
+:108630000068401C002834D0854800682421484345
+:10864000844909684018456800F052FA019084484E
+:1086500004682146019800F04EFAC61C8248007852
+:10866000002801D1FFF712FDAE4201D2284600E0FA
+:10867000304604192402240A20467C49086000BFC1
+:1086800000F036FA00900199009800F034FAC71C07
+:108690002046019900F02FFA874201D9FFF7EFFD3C
+:1086A00001E0FFF70BFDFEBDFEB50020C0430190C9
+:1086B0006B480468664807686846FFF74AFE054647
+:1086C000FFF770FE0646002D05D001AA214600984E
+:1086D000FFF7F6FE01260198FFF724FF002800D0DF
+:1086E0000126002E02D03846FFF7A0FFFEBD4170E4
+:1086F000704770B502460B465078411C907888420E
+:1087000000D100211078884201D1002070BD19608D
+:1087100050781826704355682C182046F6E7FFB5A8
+:1087200081B005460E461746E9004C4A1268881883
+:108730006946FFF7DEFF0446002C02D1042005B095
+:10874000F0BD01202070666000F0D2F9A060E76003
+:10875000049820610A986061E900404A126888180C
+:108760000099FFF7C4FFFFF78FFD0020E7E7F8B59A
+:1087700004460E46E100394A126888186946FFF738
+:10878000B8FF0546002D01D10420F8BD0220287055
+:108790006E60E100314A126888180099FFF7A7FF60
+:1087A000FFF772FD0020F0E738B50446E1002B4AE0
+:1087B000126888186946FFF79CFF0546002D01D115
+:1087C000042038BD03202870001F6860E100234AA0
+:1087D000126888180099FFF78AFFFFF755FD0020FF
+:1087E000EFE710B5002022490860002120484160D1
+:1087F0008160C16000201E49403908604860FFF771
+:1088000066FD10BD10B5FFF74FFF10BDFFB581B07D
+:108810000E4617461C4620468107890F002901D1C4
+:10882000012100E00021002902D1072005B0F0BDA0
+:10883000002C01D10720F9E7FFF740FC0D490A9809
+:1088400008600D48067003480460002523E000001E
+:1088500094200020882000208C20002090200020E0
+:1088600098200020FFFF7F00AC2000204015014031
+:1088700040110140A8200020842000200021242055
+:1088800068439F4A12681154242068439C4A126826
+:10889000801801756D1CB542F0DB2420704304196B
+:1088A00003209849087098480460183400250CE0AB
+:1088B000E900954A1268881800210170417087709C
+:1088C0004460182179430C196D1C032DF0DB002046
+:1088D000C0438E49086000208D4908708D4908709A
+:1088E000142000F0D3F803211420FFF78FFB14208D
+:1088F00000F0BEF80198FFF7C0FB00F0F9F88649D8
+:108900000860002092E770B503460C467C4800687A
+:10891000002801D1082070BD002A01D10720FAE704
+:10892000002B01D10720F6E700211CE0242048435A
+:10893000734D2D68285C002814D10125242048435C
+:108940006F4E36683554242048436D4D2D684019CC
+:108950004470242048436A4D2D6840198261196093
+:108960000020D8E7491C6D4800788142DEDB0420F6
+:10897000D1E710B500F092F8012802D0032804D105
+:1089800001E0002403E0012401E0022400BF00BF55
+:10899000204610BDFEB504460D46164658480068F0
+:1089A000002801D10820FEBD5C480078844201D235
+:1089B000052D01D20720F6E72420604350490968BD
+:1089C000085C012801D00820EDE7242060434C49D1
+:1089D000096840184078012801D1284600E00020AD
+:1089E0000746FFF7C6FF3B462A4621460096019000
+:1089F000FFF795FED7E770B50446414800680028A8
+:108A000001D1082070BD45480078844201D3072079
+:108A1000F8E7242060433A490968085C012801D03E
+:108A20000820EFE7FFF7A5FF05462146FFF79FFE69
+:108A3000E8E710B532480068002801D1082010BDD1
+:108A4000FFF797FF0446FFF7AFFEF8E710B50446BF
+:108A500000F04EF82060002010BD70B505460E46AF
+:108A600014463146284600F046F82060002070BDCC
+:108A7000C206D20E01219140294A11607047C206F8
+:108A8000D20E01219140274A11607047C206D20ED2
+:108A900001219140234A80321160704710B522486D
+:108AA0004068C105C90D002920D00A46103A50B2CD
+:108AB00000280DDA1C4B1C330407240F083CA408C3
+:108AC000A4001B598407E40EE3401B069B0F0BE038
+:108AD000134B032424021B198408A4001B59840788
+:108AE000E40EE3401B069B0FD8B210BD0420FCE748
+:108AF0000E48406870470246501A0002000A70474C
+:108B0000882000208C20002090200020942000202D
+:108B1000A4200020A52000209820002084200020F0
+:108B200000E100E000E200E000ED00E0001501409F
+:108B300070B506460C46154629462046FDF7E8FB6B
+:108B400070BDF8B55A480078002800D1F8BD5948E2
+:108B50000068002801D1012000E0002005465648A9
+:108B60000068002801D1012000E00020064600BF77
+:108B7000002D14D1684651DF0446052C01D1012592
+:108B80000CE0002C06D000BF4CA269212046FDF766
+:108B9000B1FB03E0474900980968884700BF002EF1
+:108BA0001AD154480088009069465348006861DF34
+:108BB0000446052C01D101260DE0002C06D000BF93
+:108BC0003EA280212046FDF795FB04E04A4800685C
+:108BD00039490968884700BF002D02D0002E00D017
+:108BE00000E0C5E700BF00BFB0E7FFB581B00C46AD
+:108BF00016461F46002C02D1072005B0F0BD2046C6
+:108C00008107890F002901D1012100E000210029FD
+:108C100001D10720F1E7384804603648068037481C
+:108C200007603749019810DF0546002D01D028461E
+:108C3000E3E701201E490870162026DFDDE710B5A6
+:108C400011DF0446002C01D0012000E0002018496B
+:108C50000870204610BD0146002901D10E20704742
+:108C6000154801600020FAE70146002901D10E20D5
+:108C70007047104801600020FAE770B51F4800688F
+:108C8000002811D01D4800688047044600BF2546D3
+:108C9000002D07D000BF09A2FF2133312846FDF780
+:108CA00029FB00BF00BF01E0FFF74BFF70BD0000D4
+:108CB000BA200020C0200020BC2000202E2E5C2ED8
+:108CC0002E5C2E2E5C2E2E5C2E2E5C536F757263E6
+:108CD000655C73645F636F6D6D6F6E5C736F6674FC
+:108CE0006465766963655F68616E646C65722E6346
+:108CF00000000000B8200020B4200020B020002098
+:108D0000318B010001464888B74A1288904206DB41
+:108D10004888B54A5288904201DC01207047002003
+:108D2000FCE738B50546B1480088B14988423BD0D8
+:108D3000B0480078401CAF490870AF48007B097804
+:108D400088420FDBA849A948008875DF0446002C3B
+:108D500007D0A9488069002803D0A7488169204628
+:108D6000884721E00020A3490870A348007C002820
+:108D70000FD03B219D48008876DF0446002C07D0A9
+:108D80009D488069002803D09B4881692046884718
+:108D900000BF99484069002806D0002000909648FE
+:108DA00041696846884700BF38BD70B505461C223A
+:108DB00029469148FDF7D8F90020904908702868A5
+:108DC00000280BD008228848296802F099F98648C3
+:108DD0007ADF0446002C08D0204670BD82487BDF35
+:108DE0000446002C01D02046F7E70020C0437F490D
+:108DF000088000207F490870814A00218148FFF7E0
+:108E000082FDEAE710B57F480068FFF7F4FD10BD6A
+:108E100038B57D48FFF776FF002829D177480078DC
+:108E200000280AD074484069002820D00020009013
+:108E3000714841696846884719E06E480078002803
+:108E400002D16D48456801E06B48856800222946DB
+:108E50006C480068FFF79EFD0446002C07D066486A
+:108E60008069002803D064488169204688470AE069
+:108E700061484069002806D0012000905E484169A1
+:108E80006846884700BF00205C49087038BD10B5AF
+:108E90000446A08855490880082221460E315A48C8
+:108EA00002F02EF90020534908705348C089002869
+:108EB00001D1FFF7ADFF10BD70B505460020C043DE
+:108EC0004A49088000204B4908704E480068FFF767
+:108ED00092FD0446002C07D047488069002803D043
+:108EE000454881692046884770BD70B50546AC1D70
+:108EF00020884149C989884220D1208B02281DD170
+:108F000000BF00BFA17EE27E120211430846C107E6
+:108F1000C90F002902D0FFF77BFF0FE03948006836
+:108F2000FFF769FD0646002E07D033488069002808
+:108F300003D0314881693046884700BF70BD10B505
+:108F400004460822A11D304802F0DAF8FFF760FF5E
+:108F500010BD10B504462088102806D0112808D06E
+:108F600012280ED0502810D107E02046FFF78FFFBF
+:108F70000CE02046FFF7A0FF08E02046FFF7B5FF12
+:108F800004E02046FFF7DBFF00E000BF00BF10BD9C
+:108F900038B5044608222146134802F0B1F81248B9
+:108FA0007ADF0546002D1CD11748FFF7ABFE0028DD
+:108FB0000BD10120114908700B490C48008875DF5E
+:108FC000054601200B4908700BE00B48406900285A
+:108FD00006D001200090084841696846884700BFD4
+:108FE0000025284638BD0000C4200020CE200020E7
+:108FF000FFFF0000CC2000208C240020DC2000207B
+:10900000238D0100D8200020D0200020002314212F
+:109010004143FC4A53541421414389184B80012396
+:109020001421414389188B6000231421414389187E
+:10903000CB601421414389180B61142141438918E5
+:109040008B80704710B50020EF490880EF49087009
+:10905000EC49091F087048708870002403E020461E
+:10906000FFF7D4FF641C0A2CF9D310BDFFB583B001
+:1090700004460F4615460326E448007800280BD125
+:10908000002D06D10698002803D10220DF49087080
+:1090900002E00120DD490870DC480078030002F09E
+:1090A000BCF907FD05314E84BAD7FD00D8484069A8
+:1090B000401C002808D001221207126991B2D44A3C
+:1090C000506902F0C9F802E0012000074069401E23
+:1090D00001210907096989B248430290012000076C
+:1090E00000690004820C01200007006980B2784307
+:1090F0000146029821DF0646002E02D10220C34914
+:109100000870D0E0384620DF0646002E16D1002D2C
+:1091100011D1069800280AD12078042803D107200D
+:10912000BA4908700AE00520B849087006E0042032
+:10913000B649087002E00320B4490870B3E0B448AF
+:109140004069401C002808D001221207126991B220
+:10915000AF4A506902F080F802E001200007406940
+:10916000401E01210907096989B248430121090705
+:109170000290096989B2794308462A46029921DF9B
+:109180000646002E10D1069800280AD1207804281F
+:1091900003D106209D49087006E005209B49087010
+:1091A00002E00420994908707DE099484069401C1C
+:1091B000002808D001221207126991B2944A50691E
+:1091C00002F04AF802E0012000074069401E012138
+:1091D0000907096989B24843A90040186188401805
+:1091E000012109070290096989B27943AA00891807
+:1091F00062888818069A029921DF0646002E09D156
+:109200002078042803D106208049087002E0052058
+:109210007E49087047E001200007006980B278436A
+:10922000AB00C0180290608882082169029821DF93
+:109230000646002E0BD1002D06D10698002803D13A
+:1092400007207249087002E00620704908702AE081
+:109250006F484069401C002808D00122120712699B
+:1092600091B26B4A506901F0F7FF02E0012000075C
+:109270004069401E01210907096989B248430190EC
+:1092800001200007006981B2019801F0E5FF02901A
+:1092900020DF0646002E04D1072000E002E05B49F3
+:1092A000087000E000BF00BF304607B0F0BDF0B569
+:1092B00085B00F275348001F0078142148435149B7
+:1092C0004418E5682078022804D0042823D0052813
+:1092D0006FD16FE0206903904B4800888602608858
+:1092E000801B0490039880190390A0888019451969
+:1092F000012189020498884206D2049882082846EF
+:10930000039921DF074605E0FF2201322846039931
+:1093100021DF074681E0A06800013E49401800892E
+:109320000490A0680001401840890390A0680001E3
+:109330004018406802900298A84205D10399049809
+:1093400048436188884202D0A06802280DD10120DC
+:109350000007006981B2284601F07EFF2A4909888A
+:109360004618304620DF074622E00120000700694A
+:1093700081B2284601F070FF06460120000700690F
+:1093800080B27043281A800801900120000700690C
+:1093900080B2711C484361884919401A80080090C6
+:1093A00031462046009B019AFFF760FE074600BF4A
+:1093B00033E031E001200007006981B2284601F066
+:1093C0004BFF0646A088411901200007006980B2C2
+:1093D0007043081A80080490A088401961884218D8
+:1093E00001200007006980B2711C4843801A800880
+:1093F000039031462046039B049AFFF737FE074649
+:109400000BE00000AC240020E8200020EB2000202E
+:10941000001000107425002000BF00BF002F02D1F3
+:109420000120FE498870384605B0F0BDFFB581B017
+:10943000064617460024F94840780A2837D0F748EE
+:109440000078F64949784018C4B20A2C02DB20465D
+:109450000A38C4B214206043F049091D0E54142088
+:10946000604340180761142060434118029805C802
+:10947000CA60886014206043E84A121D801804996D
+:1094800041801420604380180A9981800025101FB4
+:109490008078002805D1FFF70AFF0546112D00D17D
+:1094A0000025DE484078401CDC49487000E0042577
+:1094B000284605B0F0BDF8B50446D8480078142118
+:1094C0004843D649091D0E5C081F00781421484303
+:1094D000D249091D401880680001D1490D58CF4874
+:1094E000007814214843CD49091D40184088091FC0
+:1094F0000090097814225143C84A121D89180B693B
+:10950000111F097814225143C44A121D89180846B4
+:10951000083022463146A847F8BD10B50024BF48A0
+:109520004078002809DDFFF7C2FE0446002C04D075
+:10953000112C02D02046FFF7BEFF204610BDFEB51D
+:1095400005460026B548807801287DD10020B34922
+:109550008870B4480078082811D1022D03D100206A
+:10956000B049087002E00020AF490870FFF7D5FF4E
+:109570000646002E02D03046FFF79DFFFEBD022DAD
+:1095800002D0032D79D174E0A44800781421484317
+:10959000A249091D4418A5480088401CA349088019
+:1095A0002078022807D10846008880026188884216
+:1095B00001DB012000E0002007462078052805D1C6
+:1095C00098480078072801D1012000E0002002908F
+:1095D0002078042805D193480078072801D101207C
+:1095E00000E000200190207804280BD18F480088EB
+:1095F00080026188884205DB8A480078002801D112
+:10960000012000E0002000900298002807D1019876
+:10961000002804D10098002801D1002F1FD000207D
+:10962000804908703046FFF746FF7C490878FFF70D
+:10963000EDFC00207D49088078484078401E77493D
+:1096400048700846007800E01AE0401C08700846A0
+:1096500000780A2803DB084600780A380870FFF70C
+:109660005CFF0646002E02D03046FFF724FF05E0DF
+:1096700003E00D20FFF71FFF00E000BF00BF00BFA9
+:1096800000BF7BE770B5FFF7DDFC0020684908806C
+:1096900068484069401C002808D00122120712695E
+:1096A00091B2644A506901F0D7FD02E00120000741
+:1096B0004069801E01210907096989B248435E4952
+:1096C000086000205949088000BF0DE00021020118
+:1096D000534B99500201D21811810201D218918185
+:1096E0000201D2185181401C0028EFD008204D49BA
+:1096F000087050484069401C002808D00122120719
+:10970000126991B24B4A506901F0A6FD02E00120B6
+:1097100000074069401E01210907096989B24843D1
+:10972000064601200007006981B2304601F094FD31
+:10973000054620DF0446002C04D1012037498870FB
+:1097400039490870204670BDF8B504460D4600BF83
+:1097500035480078002801D10820F8BD00BF002C52
+:1097600001D10E20F9E7002D01D10E20F5E7206888
+:10977000002801D10E20F0E7A0880121090709691E
+:1097800089B2884202DCA088102801DA0720E4E7C9
+:10979000E08800281ED027484069401C002808D0D7
+:1097A00001221207126991B2224A506901F054FD58
+:1097B00002E0012000074069401E012109070969F4
+:1097C00089B24843E188A28851431B4A126889182C
+:1097D000884201D20720C0E720798007800F002847
+:1097E00001D00720B9E712480088012801D10420E0
+:1097F000B3E70F48008828600F48006868600C498C
+:1098000009880901064A8918686848600849098872
+:10981000090120685050A08805490FE0A8240020C5
+:1098200074250020EB200020EA200020E820002002
+:10983000E020002000100010E42000200988090129
+:1098400089180881E088FE4909880901891848813A
+:109850000027A688E1884E4300BF781C87B201200C
+:109860000007006980B2B04205D2012000070069FC
+:1098700080B2361A00E0002601200007006980B29D
+:10988000F04909684018EF490860012000070069A5
+:1098900080B2B042E1D9EA4800880001EA494018A4
+:1098A0008781E7480088401CE5490880002054E78C
+:1098B0003CB5034600BFE5480078002801D10820E8
+:1098C0003CBD00BF002B01D10E20F9E7002A01D1D9
+:1098D0000E20F5E71868002805D118680001DA4C59
+:1098E0002058002801D10720EAE75C681868019435
+:1098F0000090019C18680001D34D4019008948432D
+:1099000020180190009800012C4600194089009C05
+:109910002401641924896043009C2401641964684B
+:109920000019019CA04201D80720C9E7019C0098BA
+:10993000546010600020C3E7F8B504460E46154693
+:109940001F4600BFC1480078002801D10820F8BD9B
+:1099500000BF002E01D10E20F9E7002C01D10E200E
+:10996000F5E72068002805D120680001B6490858AD
+:10997000002801D10720EAE720680001B249401819
+:10998000408921680901B04A89180989484321683A
+:1099900009018918496840186168884201D8072080
+:1099A000D5E7002D06D020680001A749401800899E
+:1099B000A84201DA0720CAE7E81921680901A24A8A
+:1099C00089180989884201DD0720C0E7304600F088
+:1099D0005CF9002804D0384600F057F9002801D17E
+:1099E0001020B4E7606800F050F9002801D1102081
+:1099F000ADE72B463246214602200097FFF716FDC1
+:109A0000A5E7F8B504460E4615461F4600BF8F4829
+:109A10000078002801D10820F8BD00BF002E01D138
+:109A20000E20F9E7002C01D10E20F5E72068002870
+:109A300005D12068000184490858002801D1072079
+:109A4000EAE720680001804940184089216809013F
+:109A50007D4A89180989484321680901891849689C
+:109A600040186168884201D80720D5E7002D06D04C
+:109A700020680001744940180089A84201DA0720D3
+:109A8000CAE7E819216809016F4A891809898842DB
+:109A900001DD0720C0E7304600F0F7F8002804D0C9
+:109AA000384600F0F2F8002801D11020B4E76068D1
+:109AB00000F0EBF8002801D11020ADE72B4632462C
+:109AC000214605200097FFF7B1FCA5E7FFB581B05F
+:109AD0000C4615461E4600BF5C480078002802D19F
+:109AE000082005B0F0BD00BF002C01D10E20F8E722
+:109AF0000198002801D10E20F3E72068002805D145
+:109B00002068000150490858002801D10720E8E7E3
+:109B1000206800014C4940184089216809014A4ADF
+:109B2000891809894843216809018918496840183A
+:109B30006168884201D80720D3E7002D06D020684D
+:109B40000001414940180089A84201DA0720C8E70E
+:109B5000A819216809013C4A89180989884201DD50
+:109B60000720BEE7019800F090F8002804D03046A6
+:109B700000F08BF8002801D11020B2E7606800F0F7
+:109B800084F8002801D11020ABE7606881192A46CB
+:109B9000019801F0B5FA0095206800012A490F5894
+:109BA000002203212046019BB847002099E7F8B521
+:109BB00004460D4600BF25480078002801D1082042
+:109BC000F8BD00BF002C01D10E20F9E72068002865
+:109BD00005D1206800011C490858002801D1072040
+:109BE000EEE7206800011849401840892168090102
+:109BF000154A891809894843216809018918496863
+:109C000040186168884201D80720D9E7606800F0F1
+:109C10003CF8002801D11020D2E723681B010A4F2D
+:109C2000DB1962685B68D01A226812013B46D218C1
+:109C3000118901F011FB00290AD00720C0E70000BC
+:109C4000E0200020E420002074250020EA200020ED
+:109C500000202B460246214600900420FFF7E6FB39
+:109C600006463046ACE7014600BF0C4800780028A5
+:109C700001D10820704700BF002901D10E20F9E76B
+:109C80000748407808600020F4E701468807800F05
+:109C9000002801D1012070470020FCE7EA200020C5
+:109CA000A824002070B503460C460820002115E0CA
+:109CB0000C254D43FE4EAD19AD7A2540002D0CD03C
+:109CC000FC4DAB4205D00C254D43AD192D899D426D
+:109CD00002D11160002003E00520491C0029E7D0D3
+:109CE00000BF70BD10B5044618216143F24A8918BF
+:109CF00008461130062101F0ABFA18206043EE4906
+:109D000040180021016041608160C160FF2118207E
+:109D10006043E94A80180174182060438018C175B7
+:109D2000204601F00EF910BD7CB50546A9B26A4681
+:109D3000E248FFF7BDFD0446002C09D14C216846DE
+:109D4000FFF735FF0446002C02D12846FFF7CAFF73
+:109D500020467CBDF8B505460E46052700240FE0D9
+:109D600018216143D44A8918084610300722294631
+:109D700001F076F9002802D13460002702E0641C6B
+:109D8000072CEDD300BF3846F8BD10B504460121BD
+:109D90000C206043C64A80188172891E0C206043E3
+:109DA00080180181FF210C2060438018C1720C21B2
+:109DB00061438818072101F04BFA10BD10B5044625
+:109DC000002010BDF0B587B007460C461420019066
+:109DD00061780C225143B64A8918088901AA02A960
+:109DE000AADF0646002E46D1617818225143B44AB4
+:109DF0008918081D69468A8802A901F031F90028EE
+:109E000012D160780C214843A9494018807A022178
+:109E1000084002282FD0617818225143A84A881898
+:109E2000182101F017FA26E0607818214843A44968
+:109E30000858002801D0A34D00E0A34D6946898849
+:109E40006078182250439E4A11506178182251437D
+:109E50009B4A8918081D69468A8802A9FCF784F97B
+:109E6000607818225043964A8118302318223846C9
+:109E7000A847064600BF002007B0F0BD02460020FC
+:109E8000704770B505460C466178182251438C4ADC
+:109E90008818302318222946FFF718FE0646002EA0
+:109EA0000DD160781821484385490858401C002886
+:109EB00005D10021607818225043814A1150304664
+:109EC00070BD024600207047F8B504460026002702
+:109ED0006078182148437A49085800281FD06078D4
+:109EE0000C21484372494018807A4021084040289C
+:109EF00015D160780C2148436D494018807A2021A3
+:109F0000084020280BD06078182148436C4940183D
+:109F1000061D6078182148436949085A87B261785C
+:109F20000C225143624A891808893A463146A9DF12
+:109F30000546002D00D0654D2846F8BD014600209D
+:109F400070470146002902D1604833387047FF202E
+:109F5000087048708870C8700020F7E7FFB58BB0B4
+:109F60000F461D4600BF5A480068002801D10FB0B7
+:109F7000F0BD00BF14984C2800D9F8E701200290EA
+:109F800007A8FFF7DEFF064600BF0196019E002EE0
+:109F900006D000BF4F4A50490198FCF7ABF900BF0B
+:109FA00000BF00216846017708A801700695149843
+:109FB00005900020019017E0019881B203AA3F4864
+:109FC000FFF776FC0646002E0BD1082203A90B985A
+:109FD00001F046F8002804D10198C1B268468177A3
+:109FE00005E00198401C019001980728E4D300BFC8
+:109FF0006846807FFF2875D0042F0AD114984C281A
+:10A0000003D1202008A9087082E0402008A9087028
+:10A010007EE02949681A182101F01EF90446072C30
+:10A020001DD205A809900D98002810D16946887F97
+:10A0300001218140294A12681140002901D00121E3
+:10A0400000E00021002902D10020029060E0694672
+:10A05000887F00F076FF202008A9087058E020498A
+:10A06000681A182101F0F8F80446002C0DD105A853
+:10A070000990202108A8017068464477807F182144
+:10A0800048430D494018069042E00D49681A1821CE
+:10A0900001F0E2F80446002C35D1302108A8017007
+:10A0A000684644770121C1770C20604300491AE0DB
+:10A0B0005C260020FFFF000084250020EC2000200B
+:10A0C00044260020039A01003999010041800000D4
+:10A0D0006826002078B40100BC040000F4200020B1
+:10A0E0002C26002036E04018807A02210840022801
+:10A0F0000ED06946497F18225143FB4A881818211F
+:10A1000001F0A8F804E000200290402008A908709F
+:10A11000029801281ED1042F06D108A9087803212E
+:10A12000084308A908700EE0032F06D108A9087893
+:10A130000121084308A9087005E008A90878022150
+:10A14000084308A9087005A8099008A907A80D9A4E
+:10A1500000F03AFF00BF0AE77FB50646002E02D1A5
+:10A16000E24804B070BD00240BE000BF00210C20C9
+:10A170006043DF4A11500C2060438018817200BF99
+:10A18000641C002CF1D0002403E02046FFF7FDFD05
+:10A19000641C002CF9D01821D34801F05BF800248E
+:10A1A00006E02046FFF79EFDFF20D2490855641CBB
+:10A1B000072CF6D34C21684681810721C181CE4806
+:10A1C0000290CE4902A8FFF7BFFA0546002D30D114
+:10A1D0000120CB4908703078002821D100241CE0F0
+:10A1E000A1B26A46C548FFF763FB0546002D0FD1B3
+:10A1F00018216143C34A8818002318226946FFF7D3
+:10A2000065FC0546002D07D00020BD49087006E01A
+:10A210000020BB49087002E0641C072CE0D308E072
+:10A220006A469089D289504381B2B448FFF7BFFC97
+:10A230000546284695E770B505460C4600BFB04870
+:10A240000078002802D1A948801F70BD00BF002DF2
+:10A2500001D1A648F9E7002C01D1A448F5E7206810
+:10A26000002801D1A148F0E7A148006800280FD1DB
+:10A270009F49206808600622A11D9D48001D00F02E
+:10A280003FFF20799A49887200202870002601E05B
+:10A29000964E0A3E3046D8E770B5044600BF98484F
+:10A2A0000078002802D19148801F70BD00BF002CAB
+:10A2B00001D18E48F9E700BF2078012806DA20781E
+:10A2C0000C2148438A490858002802D18748801F3A
+:10A2D000EBE700BF00BF6078012807DA60780C2147
+:10A2E000484389494018807A012802D17F48801C60
+:10A2F000DBE700BF7D4DAD1F60780C2148438249EC
+:10A300004018807A02210840022809D161780C2285
+:10A3100051437D4A891808897549091D7EDF054624
+:10A320002846C2E7024600BF75480078002802D1DF
+:10A330006E48801F704700BF002A01D16B48F9E7C3
+:10A34000002901D16948F5E700BF1078012806DA35
+:10A3500010780C235843664B1858002802D16348E4
+:10A36000801FE7E700BF00BF5078012807DA507868
+:10A370000C235843644BC018807A012802D15B48F3
+:10A38000801CD7E700BF50780C2358435E4BC018A1
+:10A39000807A04231840002802D0012008700EE0C3
+:10A3A00050780C235843584BC018807A40231840EB
+:10A3B000002802D00220087001E000200870002070
+:10A3C000B8E7F0B5044600BF4D480078002802D138
+:10A3D0004648801FF0BD00BF002C01D14348F9E77B
+:10A3E000002901D14148F5E70868002801D13F481C
+:10A3F000F0E78868002801D13C48EBE700BF2078EF
+:10A40000012806DA20780C256843394D28580028A1
+:10A4100002D13648801FDDE700BF00230022002064
+:10A4200024E018254543374EAD19ED7DED07ED0FBE
+:10A43000002D08D118254543AD1997008E68F551B8
+:10A440002C4EB054521C182545432E4EAD19ED7DAF
+:10A4500002263540002D08D118254543294EAD1957
+:10A4600010359F000E68F5515B1C401C072805D273
+:10A470000D799D4202D90D7B9542D2D80B710A739A
+:10A480000020A7E7024619488230704770B504469D
+:10A4900000BF1B480078002802D11448801F70BDFF
+:10A4A00000BF002C01D11148F9E700BF2078012836
+:10A4B00006DA20780C2148430D490858002802D1BB
+:10A4C0000A48801FEBE700BF00BFA078072807DA23
+:10A4D000A078182148430B494018C07DFF2815D1AA
+:10A4E0000248801CDBE70000442600200E800000AC
+:10A4F00068260020F92000205D9F0100EC2000204C
+:10A50000F8200020842500205C26002000BFA078D1
+:10A51000FFF70AFC05462846C1E770B5044600BFB0
+:10A52000FE480078002801D1FD4870BD00BF002C16
+:10A5300002D1FB48801DF8E700BF2078012806DA29
+:10A5400020780C214843F7490858002801D1F448E5
+:10A55000EBE700BF002600250BE018206843F24916
+:10A560004018C07DFF2803D02846FFF7DDFB0646D4
+:10A570006D1C072DF1D33046D7E7FEB504460D46D6
+:10A5800000BFE6480078002801D1E548FEBD00BFC5
+:10A59000002C02D1E248801DF8E7002D02D1E048EE
+:10A5A000801DF3E700BF2078012806DA20780C210F
+:10A5B0004843DC490858002801D1D948E6E700BFE4
+:10A5C00000BF6078012807DA60780C214843D7493A
+:10A5D0004018807A012802D1D1480830D6E700BF60
+:10A5E00000BFA078072807DAA07818214843CE4991
+:10A5F0004018C07DFF2802D1C9480830C6E700BF17
+:10A60000A868002812D0686800280FD0686814284D
+:10A610000CD22878012809D1637818277B43C44FCE
+:10A62000DB19181D6A68A96800F06AFDA1786A46FE
+:10A63000C048FFF73DF9064628788000BE490A5811
+:10A64000214668469047064630469FE77CB504465B
+:10A650000D4600BFB1480078002801D1B0487CBD4C
+:10A6600000BF002C02D1AE48801DF8E7002D02D1BA
+:10A67000AB48801DF3E700BF2078012806DA207878
+:10A680000C214843A7490858002801D1A448E6E70F
+:10A6900000BF00BFA078072807DAA0781821484338
+:10A6A000A1494018C07DFF2802D19D480830D6E757
+:10A6B00000BF60780C2148439C494018807A0221F1
+:10A6C0000840022802D096488930C8E7A8680028C8
+:10A6D00012D1686814280FD2287801280CD160782C
+:10A6E0001821484392494018001DA860607818213D
+:10A6F00048438F4908586860A1786A468D48FFF73B
+:10A70000D7F80646287880008C490A5821466846C2
+:10A71000904706463046A2E7014600BF7F480078D2
+:10A72000002801D17E48704700BF002902D17C4833
+:10A73000801DF8E700BF0878012806DA08780C22A7
+:10A740005043784A1058002801D17548EBE700BF04
+:10A7500000BF8878072807DA887818225043724AA1
+:10A760008018C07DFF2802D16D480830DBE700BFAC
+:10A770006B488830D7E702466948893070470246FF
+:10A7800067488930704701466548893070470246FE
+:10A7900000BF62480078002801D16148704700BFBF
+:10A7A000002902D15E48801DF8E7002A02D15C48EA
+:10A7B000801DF3E700BF1078012806DA10780C231B
+:10A7C0005843584B1858002801D15548E6E700BFB8
+:10A7D000107808700020E1E7FEB504460E46A17827
+:10A7E0006A465448FFF764F80546002D49D1607861
+:10A7F0000C2148434D494018807A202108402028E8
+:10A8000001D0022E01D14E4F04E0012E01D14D4F57
+:10A8100000E04D4FA07818225043434A811800238E
+:10A8200018226846B8470546002D1AD1022E18D0C6
+:10A8300060780C2148433D494018807A20218843A4
+:10A8400061780C225143394A89188872607818223D
+:10A8500050433E4A811818231A466846B8470546B1
+:10A8600000BF022E0DD020780C2148432D494018FE
+:10A87000807A800030490A5821466846904705464C
+:10A8800000BFFEBD70B504460D4600BF23480078EA
+:10A89000002801D1224870BD00BF002C02D1204801
+:10A8A000801DF8E7002D02D11D48801DF3E700BF91
+:10A8B0002078012806DA20780C21484319490858E5
+:10A8C000002801D11648E6E700BF00BFA07807289E
+:10A8D00007DAA0781821484313494018C07DFF28A3
+:10A8E00002D10F480830D6E700BF6078FF2839D181
+:10A8F0002878022836D0A178182251430A4A8918AC
+:10A90000084610300722294600F0FAFBA078012102
+:10A9100081400F4A126811430D4A1BE0F8200020C5
+:10A920000880000068260020842500205C26002086
+:10A9300044260020EC20002044B4010054B401005F
+:10A94000039A010039990100BD9D01002C260020C9
+:10A95000F4200020116000BF02212046FFF73CFFD9
+:10A96000002600E0FE4E304695E770B504460E46E0
+:10A9700000BFFC480078002802D1F948401C70BD97
+:10A9800000BF002C02D1F648C01DF8E7002E02D10E
+:10A99000F348C01DF3E700BF2078012806DA2078CD
+:10A9A0000C214843F0490858002802D1EC48401CCB
+:10A9B000E5E700BFEA4DAD1EA078FF2817D160780B
+:10A9C000FF282AD060780C214843E8494018807A53
+:10A9D00002210840022820D160780C225043E34A2B
+:10A9E00081180722304600F08BFB002515E0A07887
+:10A9F00018214843DE494018C07D02210840002844
+:10AA00000BD1A07818225043D94A80180146103142
+:10AA10000722304600F074FB00252846AFE7F0B56A
+:10AA20008BB004460026FF20049000BFCD4800787C
+:10AA3000002801D10BB0F0BD00BF00BFCA480068BC
+:10AA4000002800D1F6E700BF08A8FFF77AFA05460C
+:10AA500000BF2F46002F06D000BFC64AC649384661
+:10AA6000FBF748FC00BF00BF0020039009900025C1
+:10AA7000201D0690342168468183FF2108A881703B
+:10AA80000021017000200A902088102813D0A0888F
+:10AA90000AAA0221FFF706F90546002D0BD10C2169
+:10AAA0000A984843B1494018C17A08A881700A98A9
+:10AAB000C1B208A84170208814287DD006DC102877
+:10AAC0000BD0112879D0132878D1DAE0172876D066
+:10AAD000182875D05228F7D10DE20AAF3A46012165
+:10AAE000A648FFF7DFF802900298002807D102215C
+:10AAF0000C22386850439D4A8018817201E004207E
+:10AB0000029002980546002D71D101261121684658
+:10AB100001750A98C1B208A84170A1880C220A9850
+:10AB20005043924A8018018102210C220A98504316
+:10AB30008E4A801881720C220A9951438B4A8818D8
+:10AB40000722A11D00F0DCFA607B304000280AD00B
+:10AB5000607B40088A49085CFF2809D0607B400878
+:10AB6000085C049004E004A9A01DFFF7F3F8054673
+:10AB7000002D3CD10498C1B20C220A9850437B4A64
+:10AB80008018C1720C210A98484311464018807AF7
+:10AB9000082108430C220A995143744A8918887283
+:10ABA00008A904988870049881B201AA7548FEF734
+:10ABB0007FFE0546002D04E04EE019E0A4E1A1E08F
+:10ABC00078E113D118220A9951436F4A8818182343
+:10ABD0001A4601A9FEF77AFF6348807A80006B4924
+:10ABE0000A5808A901A89047054600BF00BF8CE19C
+:10ABF0000C210A9848435D494018807A0221884315
+:10AC00000C220A995143594A891888720C210A98D2
+:10AC1000484311464018807A08210840082804D18A
+:10AC2000002108A8FFF7D8FD0AE008A88078FF28CF
+:10AC300006D008A98878FFF755F8FF2008A9887082
+:10AC400010210C220A985043484A8018817201262C
+:10AC500012206946087558E10C210A984843434977
+:10AC60004018C07AFF2806D018210A984843464960
+:10AC70004018001D0390A0880022039981DF05463B
+:10AC800043E1132069460875A088374A121D002148
+:10AC90007FDF0546002D02D00995002631E00C210A
+:10ACA0000A98484331494018807A042108430C220D
+:10ACB0000A9951432D4A8918887201260C210A9855
+:10ACC000484311464018C07AFF281AD000BF16200A
+:10ACD0006946087518220A9951432D4A8918081D9A
+:10ACE000142100F0B7FA0C210A9848431F49401874
+:10ACF000807A202108430C220A9951431B4A891863
+:10AD0000887202E10C210A98484318494018807A59
+:10AD1000042188430C220A995143144A89188872E5
+:10AD20001420694608750126A079002802D0A07970
+:10AD30000990BEE018210A98484313494118E07A67
+:10AD4000887018210A9848430F494118A07A487022
+:10AD500018220A9951430C4A8918081D142219E037
+:10AD600007800000F8200020682600205C260020D4
+:10AD70008425002078B40100F3080000FFFF0000E4
+:10AD8000F9200020EC2000202C26002054B40100E3
+:10AD90004426002021460C3100F0B2F90C210A981B
+:10ADA0004843A9494018807A4021084040286BD187
+:10ADB0000C210A984843A4494018807A0821084386
+:10ADC0000C220A995143A04A891888720C210A98CA
+:10ADD000484311464018C07AFF2858D10C220A98DF
+:10ADE00050430A46811804A800F0B3F80546002D28
+:10ADF0004BD108A9049888700498C1B20C220A9813
+:10AE00005043914A8018C172E07A4007C00F002871
+:10AE100017D018220A9951438C4A881810222146CB
+:10AE2000203100F06DF918210A98484387494018ED
+:10AE3000C07D4108490018220A985043834A80186F
+:10AE4000C175E07A0007C00F002819D018220A99AE
+:10AE500051437E4A89180846103007222146303176
+:10AE600000F04EF918210A98484378494018C07DEF
+:10AE70000221884318220A995143744A8918C875D7
+:10AE8000012108A8FFF7A8FC13E0099511E00C21A7
+:10AE90000A9848436C494018807A202108430C22C4
+:10AEA0000A995143684A89188872002108A8FFF757
+:10AEB00093FC2AE00C210A98484363494018807AA1
+:10AEC000402108430C220A9951435F4A891888722D
+:10AED0001520694608750020099001265C48807A93
+:10AEE00080005C4A115808A888470546002D01D00B
+:10AEF0005948099009E05648807A8000554A11580F
+:10AF000008A88847099000E000BF00BF002E16D0B7
+:10AF100005A908A8099A00F057F86846007D12288C
+:10AF20000DD100BF0C210A98484347494018807A48
+:10AF3000012802D00A98FEF728FF00BF00BF00BF1B
+:10AF400078E501218140454A12688A4343490A60F5
+:10AF50007047F8B505460E463F4FBF1C00242EE053
+:10AF60001820604339494018C07DFF2826D1307829
+:10AF7000022814D0182060434018C07D0221884365
+:10AF800018216143314A8918C8751821614389180D
+:10AF9000084610300722314600F0B2F80BE01820C6
+:10AFA00060432A494018C07D410849001820604389
+:10AFB000264A8018C1752C70002702E0641C072CFB
+:10AFC000CED300BF3846F8BDF8B504460D46164648
+:10AFD0001F480768324629462046B847F8BD024652
+:10AFE0000020704770B505460E4631462846FEF7EC
+:10AFF000E9FE0446002C02D100BF00BF04462046F3
+:10B0000070BD02460020704770B505460E463146B9
+:10B010002846FEF736FF0446002C02D100BF00BFD1
+:10B020000446204670BD01460020704770B50546B5
+:10B030002846FEF749FF0446002C02D100BF00BF9E
+:10B040000446204670BD00005C26002084250020B8
+:10B050006826002064B4010041800000F420002034
+:10B06000034610B50B439B070FD1042A0DD308C824
+:10B0700010C9121FA342F8D018BA21BA884201D9C8
+:10B08000012010BD0020C04310BD002A03D0D3070B
+:10B0900003D0521C07E0002010BD03780C78401C40
+:10B0A000491C1B1B07D103780C78401C491C1B1B37
+:10B0B00001D1921EF1D1184610BD000030B5441CDC
+:10B0C00003E00178401C00290DD08107F9D10B4B1A
+:10B0D000DD0104C8D11A91432940FAD0001B0A06A9
+:10B0E00003D0C01E30BD001B30BD0A0401D0801E3D
+:10B0F00030BD0902FCD0401E30BD0000010101013D
+:10B10000F8B5042A2CD3830712D00B78491C03709E
+:10B11000401C521E83070BD00B78491C0370401C47
+:10B12000521E830704D00B78491C0370401C521E2A
+:10B130008B079B0F05D0C91ADF002023DE1B08C92F
+:10B140000AE0FBF711F8F8BD1D4608C9FD401C4692
+:10B15000B4402C4310C0121F042AF5D2F308C91AB8
+:10B16000521EF0D40B78491C0370401C521EEAD4C6
+:10B170000B78491C0370401C012AE4D40978017043
+:10B18000F8BD431A70B593421BD28918801804E0A9
+:10B19000491E401E0B78521E03709307F8D106E03B
+:10B1A0001039103878C9103978C01038103A102A80
+:10B1B000F6D203E0091F001F0B680360121FF9D5C8
+:10B1C00070BDFAF7D1FF70BDF8B5064614460843C6
+:10B1D000800707D0751AA54219D20819102D13D26D
+:10B1E000311907E03046FFF7CCFFF8BD401E491E7D
+:10B1F00002780A70641EF9D2F8BD471B2A46394608
+:10B20000FFF77EFF641B3846A542F6D3011B22469A
+:10B210003046FFF775FFF8BD01E004C0091F04299F
+:10B22000FBD28B0701D50280801CC90700D00270B9
+:10B23000704700290BD0C30702D00270401C491E82
+:10B24000022904D3830702D50280801C891EE3E70C
+:10B250000022EEE70022DFE7002203098B422CD315
+:10B26000030A8B4211D300239C464EE003460B4356
+:10B270003CD4002243088B4231D303098B421CD3B8
+:10B28000030A8B4201D394463FE0C3098B4201D3AA
+:10B29000CB01C01A524183098B4201D38B01C01AE2
+:10B2A000524143098B4201D34B01C01A5241030959
+:10B2B0008B4201D30B01C01A5241C3088B4201D308
+:10B2C000CB00C01A524183088B4201D38B00C01AB5
+:10B2D000524143088B4201D34B00C01A5241411ADC
+:10B2E00000D201465241104670475DE0CA0F00D0BF
+:10B2F0004942031000D34042534000229C460309B8
+:10B300008B422DD3030A8B4212D3FC22890112BA3D
+:10B31000030A8B420CD3890192118B4208D3890115
+:10B3200092118B4204D389013AD0921100E089092D
+:10B33000C3098B4201D3CB01C01A524183098B420E
+:10B3400001D38B01C01A524143098B4201D34B01F7
+:10B35000C01A524103098B4201D30B01C01A52415A
+:10B36000C3088B4201D3CB00C01A524183088B42E1
+:10B3700001D38B00C01A5241D9D243088B4201D36A
+:10B380004B00C01A5241411A00D201466346524155
+:10B390005B10104601D34042002B00D54942704754
+:10B3A00063465B1000D3404201B50020C046C04652
+:10B3B00002BD704770477047754600F023F8AE46EF
+:10B3C000050069465346C008C000854618B020B540
+:10B3D000FAF7FAFE60BC00274908B6460026C0C549
+:10B3E000C0C5C0C5C0C5C0C5C0C5C0C5C0C5403D3D
+:10B3F00049008D4670470446C046C0462046FAF7CD
+:10B400009DFE000000487047742600200149182066
+:10B41000ABBEFEE726000200704730B47446641EDF
+:10B420002578641CAB4204D3635D5B00E31830BC39
+:10B4300018471D46F8E700000D1801000F1801001D
+:10B440000A180100DFAF0100C59D01007D9E0100CB
+:10B45000E5AF010003B00100839E0100C39E01001F
+:10B4600009B0010027B00100C99E01003D9F010005
+:10B470002DB00100FFFFFFFF2E2E5C2E2E5C2E2E26
+:10B480005C2E2E5C2E2E5C536F757263655C626C55
+:10B49000655C6465766963655F6D616E6167657241
+:10B4A0005C6465766963655F6D616E616765725F37
+:10B4B0007065726970686572616C2E6300000000CF
+:10B4C000D0B4010000200020000100000461010050
+:10B4D0000024F400FFFF0100000000000000000055
+:10B4E000000000000000000000000000000000005C
+:10B4F000000000000000000000000000000000004C
+:10B50000000000000000000000000000000000003B
+:10B51000000000000000000000000000000000002B
+:10B52000000000000000000000000000000000001B
+:10B53000000000000000000000000000000000000B
+:10B5400000000000000000000000000000000000FB
+:10B5500000000000000000000000000000000000EB
+:10B5600000000000000000000000000000000000DB
+:10B5700000000000000000000000000000000000CB
+:10B5800000000000000000000000000000000000BB
+:10B5900000000000000000000000000000000000AB
+:10B5A000000000000000000000000000000000009B
+:10B5B000000000000000000000000000000000008B
+:10B5C000000000000000000000000000000000007B
+:04000005000160C1D5
+:00000001FF
diff --git a/app/src/main/res/raw/ble_app_rscs_s110_v6_0_0.hex b/app/src/main/res/raw/ble_app_rscs_s110_v6_0_0.hex
new file mode 100644
index 000000000..02b913eb7
--- /dev/null
+++ b/app/src/main/res/raw/ble_app_rscs_s110_v6_0_0.hex
@@ -0,0 +1,1198 @@
+:020000040001F9
+:10400000A03A0020C1410100D3410100D541010087
+:1040100000000000000000000000000000000000A0
+:10402000000000000000000000000000D741010077
+:104030000000000000000000D9410100DB41010048
+:10404000DD410100DD410100DD410100DD410100F4
+:10405000DD41010000000000DD410100DD41010003
+:10406000DD410100DD410100DD410100DD410100D4
+:10407000DD410100DD410100DD410100DD410100C4
+:10408000DD41010011660100DD410100DD4101005B
+:1040900033660100DD410100996A0100DD41010044
+:1040A000DD410100DD4101000000000000000000D2
+:1040B0000000000000000000000000000000000000
+:1040C00000F002F800F040F80CA030C808382418BE
+:1040D0002D18A246671EAB4654465D46AC4201D140
+:1040E00000F032F87E460F3E0FCCB6460126334232
+:1040F00000D0FB1AA246AB4633431847884800005D
+:10410000A8480000103A02D378C878C1FAD85207FC
+:1041100001D330C830C101D504680C60704700007D
+:104120000023002400250026103A01D378C1FBD8D3
+:10413000520700D330C100D50B6070471FB5C04691
+:10414000C0461FBD10B510BD04F0E5FB1146FFF7DA
+:10415000F5FF00F074FD04F0FDFB03B4FFF7F2FF80
+:1041600003BC04F001FC0000401E00BF00BF00BF04
+:1041700000BF00BF00BF00BF00BF00BF00BF00BF47
+:1041800000BFF1D17047000070B505460C461646D9
+:1041900002E00FCC0FC5103E102EFAD2082E02D32B
+:1041A00003CC03C5083E042E07D301CC01C5361F3E
+:1041B00003E021782970641C6D1C761EF9D270BD55
+:1041C0000A4802680F210A430260094880470948EB
+:1041D0000047FEE7FEE7FEE7FEE7FEE7FEE700003A
+:1041E00005480649064A074B704700002405004071
+:1041F0009F420100C1400100A02A0020A03A0020F7
+:10420000A0320020A03200202E482F4908607047BD
+:104210002E48008CC0B2012810D12C48808C000799
+:10422000000F0BD12948806AF0210840402805D1B1
+:104230002648C06A084201D1012070470020FCE7EF
+:104240002248008CC0B2012827D12048808C00076A
+:10425000000F22D11D48806AF021084205D11B4879
+:10426000C06A084201D1012070471848806AF021D5
+:104270000840102805D11548C06A084201D1012024
+:10428000F2E71248806AF0210840302805D10F4833
+:10429000C06A084201D10120E6E70020E4E710B53A
+:1042A000FFF7CEFF002805D009480A494860C81327
+:1042B00009498861FFF7ACFF002802D001200749B7
+:1042C000886010BD0024F40000200020C00F00F022
+:1042D000DFFF07C000050040006C00400006004002
+:1042E00030B513048C0023430524240707252D0231
+:1042F00064198500635130BD0F2000F0C7FC00BF7A
+:10430000BFF34F8FFB48FC49C860BFF34F8F00BF1E
+:1043100000BFFEE704460D462A462146F748FFF750
+:10432000EBFF70B5F649F74801F085FDC5B22946A7
+:10433000F54801F018F80446002C0DD0082C0BD0DD
+:10434000F248844208D0F248844205D000BFF1A26E
+:10435000BD212046FFF7D0FF70BD10B50446FFF722
+:10436000E0FF10BD10B504460120207000206070F1
+:10437000A070EB49EB4801F05EFDA080EA49EB48F4
+:1043800001F059FDA071EA49EA4801F054FD20818D
+:10439000A08803210902884201D90120A07010BD24
+:1043A0007FB505466846FFF7DDFF6946E24801F044
+:1043B000B2F90446002C0ED0082C0CD0D34884420D
+:1043C00009D0D348844206D000BFD2A2FF210531D4
+:1043D0002046FFF791FF7FBD10B5082000F04DFC8F
+:1043E000092000F04AFC0F2000F047FC10BDF8B592
+:1043F00000BF0020D14B05220321009002F01DF9DF
+:10440000054600BF2E46002E06D000BFC1A2FF21E8
+:104410001F313046FFF770FF00BF00BFC84A0121BF
+:10442000C84802F089F9044600BF2546002D06D091
+:1044300000BFB8A2FF2125312846FFF75DFF00BF6E
+:10444000C14A0121C14802F077F9044600BF254660
+:10445000002D06D000BFAFA2FF212B312846FFF769
+:104460004BFF00BFF8BD3EB500BF684600780109AC
+:104470000901491C009168460078F02188431030FA
+:10448000009000BF0A22B2A168467CDF044600BF4C
+:104490002546002D06D000BF9EA2FF213D312846B3
+:1044A000FFF72AFF00BF1120800178DF044600BF1C
+:1044B0002546002D06D000BF96A2FF214031284698
+:1044C000FFF71AFF00BF002001900290FF219131F9
+:1044D000684681804900C18000210181FF2191311E
+:1044E000418101A87ADF044600BF2546002D06D091
+:1044F00000BF88A2FF214A312846FFF7FDFE00BF1A
+:104500003EBD30B593B006210391944A07CA6B466D
+:1045100007C33C2104A804F04CF9022004900121B7
+:1045200068464175018303A9079103218185694686
+:104530000C91002104A801F0E5FB044600BF2546CC
+:10454000002D06D000BF73A2FF216B312846FFF774
+:10455000D3FE00BF1421824804F02BF900208049CB
+:1045600008704860087228200882B420488213B07E
+:1045700030BD30B599B00020169017901890169065
+:10458000052010A9488400BF10A8007F0109090177
+:10459000491C10A80177007FF0218843103010A932
+:1045A000087700BF00BF16A840790009000116A9CE
+:1045B000487116A84079F021884316A9487100BFB8
+:1045C00000BF16A8807900090001887116A88079BB
+:1045D000F021884316A9887100BF00BF10A8C07FD2
+:1045E00001090901491C10A8C177C07FF021884347
+:1045F000103010A9C87700BF00BF16A8007A0009C4
+:10460000000116A9087216A8007AF021884316A99D
+:10461000087200BF484801F050F8044600BF254624
+:10462000002D06D000BF3BA2FF2194312846FFF7A2
+:1046300063FE00BF142111A804F0BBF800BF10A84E
+:10464000407C01090901491C10A84174407CF021FB
+:104650008843103010A9487400BF00BF11A8807BA8
+:1046600000090001401C11A9887311A8807BF0216A
+:104670008843103011A9887300BF00BF11A8C07B08
+:1046800000090001C87311A8C07BF021884311A95B
+:10469000C87300BF00BF10A8007D01090901491CB3
+:1046A00010A80175007DF0218843103010A908750D
+:1046B00000BF00201190012110A80172002013906A
+:1046C000642110A8017411A90F4800F02BFE0446C4
+:1046D00000BF2546002D06D000BF0EA2FF21A63147
+:1046E0002846FFF709FE00BF402101A804F061F849
+:1046F00038E000000400FA0500ED00E0EFBEADDE9A
+:10470000E02300200C200020AC2300200330000018
+:10471000013400002E2E5C6D61696E2E6300000076
+:10472000F023002014200020002400201C20002062
+:104730001024002024200020C42300200821002071
+:104740005B4301002C200020A14301003020002009
+:104750004E6F726469635F525343000078890100B1
+:1047600098230020F9A101A801F032FB00BF10A896
+:10477000007801090901491C10A801700078F02196
+:104780008843103010A9087000BF00BF10A84078FF
+:1047900000090001487010A84078F021884310A952
+:1047A000487000BF01A800F095FB044600BF2546F5
+:1047B000002D06D000BFEA4AFF21B1312846FFF79D
+:1047C0009BFD00BF19B030BD10B55120E549086010
+:1047D00064204860012088600873E34801F01EFBF4
+:1047E0008020E24908600D20C0014860FF20813030
+:1047F000886000200873DE4801F010FB2820DD49A6
+:104800000860A02048601420886000200873DA48FF
+:1048100001F004FB1420D94908607D204860052080
+:10482000886000200873D64801F0F8FA10BD1E20F9
+:10483000D44908800846807840084000401C8870B1
+:104840000846807802218843CE49887008468078DF
+:104850001C2188430C30CB49887008468078202181
+:104860008843C84988700720C870102008717047B5
+:1048700070B5002201210904C348006801F0A3FFBC
+:10488000044600BF2646002E06D000BFB44AFF21D2
+:10489000EF313046FFF730FD00BF0125ED03002268
+:1048A0002946BA48006801F08EFF044600BF26463C
+:1048B000002E06D000BFAA4AFF21F4313046FFF790
+:1048C0001BFD00BF70BD70B5B14873DF044600BF6B
+:1048D0002546002D06D000BFA14AFF21FF31284602
+:1048E000FFF70AFD00BF082000F0D0F970BD70B5D9
+:1048F0000546286800280FD13B21A648008876DFAE
+:10490000044600BF2646002E05D000BF944AA249A7
+:104910003046FFF7F1FC00BF70BD044600BF904A6F
+:10492000112149012046FFF7E7FC30B587B01C2173
+:10493000684603F03EFF002000900520C003019070
+:104940000290032168460173944841896846C181F9
+:10495000002101749248059092480690684602F042
+:1049600032F9044600BF2546002D06D000BF7C4A20
+:10497000894922312846FFF7BFFC00BF07B030BD90
+:1049800070B505462888102806D011280ED01328A7
+:1049900025D019284BD135E0092000F077F90820FF
+:1049A00000F079F9A8887B49088041E0092000F0EF
+:1049B00072F90020C0437749088002F0BBFF04462B
+:1049C00000BF2646002E06D000BF654A72493C3122
+:1049D0003046FFF791FC00BFFFF775FF28E0694AFA
+:1049E00000216C4800887FDF044600BF2646002E69
+:1049F00006D000BF5A4A684945313046FFF77CFC73
+:104A000000BF15E0A879002810D1082000F043F974
+:104A100031DF044600BF2646002E06D000BF504AB4
+:104A2000992189003046FFF767FC00BF00E000BF16
+:104A300000BF70BD10B50446204602F03EFF21467F
+:104A4000564800F006FD2146574800F06EFB204610
+:104A500002F08DF92046FFF793FF10BD10B5044614
+:104A6000204603F074FB10BD70B500BF0023502238
+:104A70004E49082001F0C9FF054600BF2E46002E12
+:104A800006D000BF364A444981313046FFF734FC36
+:104A900000BF00BF464801F0EDFF044600BF2546B9
+:104AA000002D06D000BF2E4A3B4985312846FFF72E
+:104AB00023FC00BF3F4801F0E6FF044600BF254647
+:104AC000002D06D000BF264A334989312846FFF71A
+:104AD00013FC00BF70BD10B5032200210846FFF78C
+:104AE000FFFB032200210120FFF7FAFB10BD044663
+:104AF00000BF1B4A5721C9002046FFF7FDFBFEB54A
+:104B000003F058FB044600BF2546002D06D000BF29
+:104B1000134A2149B1312846FFF7EEFB00BF0120BF
+:104B200025490969C1400140002900D100E0002069
+:104B3000064622484069401C42D0012212071269F1
+:104B400091B21E4A506903F036FE3CE04E6F72642B
+:104B5000696353656D69636F6E647563746F72002A
+:104B600014470100E02300200C200020F023002047
+:104B700014200020002400201C20002010240020ED
+:104B800024200020062000202C200020302000209F
+:104B9000982300200420002015020000C4230020D8
+:104BA000EF4801001B490100AC23002048230020EE
+:104BB000354A01005D4A0100000500500010001058
+:104BC000012000074069401E69460870344840696A
+:104BD000401C08D001221207126991B2304A506974
+:104BE00003F0E9FD02E0012000074069C01EC1B2E8
+:104BF00068464170002001902A48029068468670FD
+:104C000002F0D6FF044600BF2546002D05D000BFA8
+:104C1000254A26492846FFF76FFB00BFFEBD70B549
+:104C20003DDF044600BF2546002D06D000BF1E4ACA
+:104C3000B72189002846FFF75FFB00BF70BDFFF773
+:104C4000CBFBFFF748FFFFF70FFFFFF758FFFFF71A
+:104C5000CEFBFFF708FCFFF754FCFFF78AFCFFF7D9
+:104C6000B3FDFFF762FEFFF7E2FDFFF701FEFFF77E
+:104C70002AFE00BFFFF7D3FFFCE701210522120740
+:104C800007231B02D2188300D150704701218140B5
+:104C9000074A9160704701218140054AD160704701
+:104CA00000100010EF4A010014470100D302000079
+:104CB0000005005070B504460D4600BF002D01D020
+:104CC000012000E000200646002E05D100BFE6A22C
+:104CD0003321FFF711FB00BF00BF00BF002C01D044
+:104CE000012000E000200646002E05D100BFDEA214
+:104CF0003421FFF701FB00BF00BF2878207029880E
+:104D00002879FF231B02194000200206080A1043DD
+:104D1000607028791B022968194000200204080CE1
+:104D20001043A07029791B0228681840002109024D
+:104D3000000E0843E07028792071287A6071190C00
+:104D4000A8680840000AA0710902A8680840000C81
+:104D5000E07170BDF8B505460E46002400BF002E78
+:104D600001D0012000E000200746002F05D100BF40
+:104D7000BDA24B21FFF7C0FA00BF00BF00BF002D4E
+:104D800001D0012000E000200746002F05D100BF20
+:104D9000B5A24C21FFF7B0FA00BF00BF3278204621
+:104DA000611CCCB22A542919708800F054F90019FA
+:104DB000C4B22919B08800F04EF90019C4B22919FB
+:104DC000F08800F048F90019C4B200BF072C01D1E7
+:104DD000012000E000200746002F05D100BFA2A25D
+:104DE0005421FFF789FA00BF00BFF8BDF0B58FB0BE
+:104DF00007460D4614461E4600BF002D01D0012077
+:104E000000E0002000900098002805D100BF96A285
+:104E10006D21FFF771FA00BF00BF00BF002C01DD5C
+:104E2000012000E0002000900098002805D100BF7C
+:104E30008DA26E21FFF760FA00BF00BF1C2107A8FA
+:104E400003F0B7FC6946087F02218843801C69464D
+:104E50000877002008900A900B900C900D9000BFEE
+:104E6000012108A881766846078700BF00200190CD
+:104E700031786846017171784171807906218843E3
+:104E8000811C684681718079082188430146684603
+:104E9000817180791021884301466846817180794B
+:104EA0004108490068468171142102A803F081FC81
+:104EB0000EA8029001A80390684604820021418256
+:104EC0008482069502AA07A974480088149BA2DF71
+:104ED0000FB0F0BD7FB5044600BF01216846817365
+:104EE0006F49818100BF6D4A03A90120A0DF0546FB
+:104EF000002D02D0284604B070BD208800280DDDAA
+:104F000068480090228823463C3367486168FFF771
+:104F10006DFF0546002D01D02846ECE720890028CA
+:104F20000EDD62480090228923463C335E48401FD4
+:104F3000E168FFF75BFF0546002D01D02846DAE760
+:104F4000208A00280EDD5A480090228A23463C33EE
+:104F50005548001F6169FFF749FF0546002D01D044
+:104F60002846C8E7208B00280EDD52480090228B8F
+:104F700023463C334C48801EE169FFF737FF054666
+:104F8000002D01D02846B6E7208C00280EDD4A48C7
+:104F90000090228C23463C334348C01E616AFFF7D1
+:104FA00025FF0546002D01D02846A4E7208D0028C6
+:104FB0000EDD42480090228D23463C333A48401E85
+:104FC000E16AFFF713FF0546002D01D0284692E75E
+:104FD000206B002813D001A8216BFFF76BFE384827
+:104FE00023463C33082201A900902F48801FFFF779
+:104FF000FDFE0546002D01D028467CE700BF606B12
+:1050000000280FD02F480090606B027923463C3374
+:1050100001682548401CFFF7E9FE0546002D01D038
+:10502000284668E7A06B002813D001A8A16BFFF702
+:1050300091FE254823463C33072201A900901A48D7
+:105040002730FFF7D3FE0546002D01D0284652E752
+:1050500000BF00204FE702460A70FF200002104008
+:1050600000124870022070472E2E5C2E2E5C2E2ED1
+:105070005C2E2E5C2E2E5C536F757263655C626CC9
+:10508000655C626C655F73657276696365735C62AB
+:105090006C655F6469732E6300000000342000209B
+:1050A0000A18000036200020292A00003E20002097
+:1050B000462000204E200020562000205E200020A8
+:1050C000662000206E200020762000208A88428200
+:1050D00070470022D24342827047F8B504460E461C
+:1050E000207D002820D0B51D2988608981421AD1F1
+:1050F000288B022817D12068002814D000BF00BFD9
+:10510000A97EEA7E120211430846C107C90F002991
+:1051100002D00020009001E0012000906946204666
+:105120002268904700BF00BFF8BD70B505460C4629
+:105130002088102804D0112807D050280FD109E06A
+:1051400021462846FFF7C2FF0AE021462846FFF71E
+:10515000C0FF05E021462846FFF7BFFF00E000BF83
+:1051600000BF70BDF0B591B004460D46207D00280B
+:1051700019D00020099000BF08A800790109090191
+:10518000491C08A801710079F0218843103008A952
+:10519000087100BF697B08A84171807906218843A6
+:1051A000801C08A988711C210AA803F002FB08A82A
+:1051B000007A02218843811C08A80172007A10211C
+:1051C0008843217D002901D0012100E0002109014F
+:1051D00010221140084308A9087200200B900D907E
+:1051E0000E90207D002801D009A800E000200F903B
+:1051F0000020109000BF01216846817377498181AA
+:1052000000BF00200290A97B68460172E97B4172D1
+:10521000807A06218843811C68468172807A082141
+:105220008843014668468172807A1021884301468E
+:1052300068468172807A4108490068468172297BFC
+:105240000191142104A803F0B4FA03A8049002A861
+:10525000059001216846018300214183012181835A
+:1052600001A80890A088A31D04AA0AA9A2DF0646E7
+:10527000002E02D0304611B0F0BDA86800284DD0F5
+:1052800000BF0121684681735549818100BF00201C
+:105290000290287C6946087200BF6846407A01097E
+:1052A000090168464172407AF02188436946487294
+:1052B00000BF6846807A06218843811C6846817257
+:1052C000807A08218843014668468172807A1021DD
+:1052D0008843014668468172807A410849006846E1
+:1052E0008172A96800F056FD0746142104A803F056
+:1052F00060FA03A8049002A805906846078300217D
+:105300004183018B81830890E08822460E3204A9F4
+:10531000A3DF0646002E03D03046ACE70020E08134
+:105320000020A8E7F8B504460D4628682060002054
+:10533000C043608228792075FF20207400BF0121BE
+:10534000684681702749018000BF221D69460120FF
+:10535000A0DF0646002E01D03046F8BD2946204683
+:10536000FFF700FFF9E733B585B004460025207C40
+:105370006946097E88422FD0012104916846007E4B
+:105380002074E08806AB04AA0021A4DF0546002DA6
+:1053900002D0284607B030BD608A1349884219D030
+:1053A000207D002816D000200090019002900390EC
+:1053B00001210491E188684601800121817000216A
+:1053C000818004A8029006A80390608A6946A6DF3F
+:1053D000054600E0082500BF2846DBE7192A000043
+:1053E000082900000F180000FFFF00008A88C28211
+:1053F00070470022D243C282704738B505460C463A
+:10540000208B022817D12868002814D000BF00BFC5
+:10541000A17EE27E120211430846C107C90F00298E
+:1054200002D00020009001E001200090694628464B
+:105430002A68904700BF38BD70B504460D46AE1DC2
+:1054400031886089814203D131462046FFF7D5FF7C
+:1054500070BD70B505460C462088102804D0112870
+:1054600007D050280FD109E021462846FFF7BEFF9C
+:105470000AE021462846FFF7BCFF05E02146284602
+:10548000FFF7DAFF00E000BF00BF70BDF7B50C46C4
+:105490001646002701257119A08800F06BF9401904
+:1054A000C5B2A2792846691CCDB232540098007E5C
+:1054B000C007C00F0AD02078002807D0012007437A
+:1054C0007119208900F056F94019C5B20098008B77
+:1054D0000221084216D06078002813D0022007432A
+:1054E0007119E0680870FF2212020240120A4A7025
+:1054F000FF2212040240120C8A70020ECA700422AB
+:105500005019C5B20098008B0421084204D0A0783D
+:10551000002801D00420074337702846FEBD30B56F
+:1055200099B005460C460020119000BF10A80079E4
+:1055300001090901491C10A801710079F021884373
+:10554000103010A9087100BF217910A8417180792D
+:1055500006218843811C10A881711C2112A803F028
+:1055600028F910A8007A10218843103010A9087279
+:10557000002013901590169011A8179000201890F5
+:1055800000BF012108A881737F496846818500BF5B
+:1055900000200A90617908A80172A1794172807A8D
+:1055A00006218843811C08A88172807A08218843DB
+:1055B000014608A88172807A10218843014608A814
+:1055C0008172807A41084900491C08A8817214211F
+:1055D0000CA803F0EEF80BA80C900AA80D9001AAF5
+:1055E00006A92846FFF752FF0146684601870021B9
+:1055F00041871421818701A81090A888AB1D0CAAAF
+:1056000012A9A2DF19B030BD70B590B005460C46A6
+:105610001C2109A803F0CDF808A9087902218843C4
+:10562000801C08A9087100200A900C900D900E9023
+:105630000F9000BF0121684681735349491C818145
+:1056400000BF00200290E17968460172217A417220
+:10565000807A06218843811C68468172807A0821FD
+:105660008843014668468172807A1021884301464A
+:1056700068468172807A4108490068468172142127
+:1056800004A803F096F866896846067131124171E4
+:1056900003A8049002A80590022168460183002116
+:1056A00041830221818301A80890A8882B460E33EC
+:1056B00004AA09A9A2DF10B070BDF8B504460E46D1
+:1056C000306820600020C043E0827089208300BFE2
+:1056D0000121684681702D49018000BF221D694665
+:1056E0000120A0DF0546002D01D02846F8BD314637
+:1056F0002046FFF714FF0546002D01D02846F5E7A8
+:1057000031462046FFF780FF0546002D01D0284690
+:10571000ECE70020EAE7F0B58BB004460F46E08ADC
+:105720001B49884222D006AA39462046FFF7AEFE22
+:105730000646059600200190029003900490E188AF
+:1057400068468180012181710021018105A80390B3
+:1057500006A80490E08A01A9A6DF0546002D04D121
+:105760006846808AB04200D00C2500E00825284613
+:105770000BB0F0BD02460A70FF200002104000127C
+:105780004870022070470000532A000014180000DF
+:10579000FFFF0000F7B584B004460F460698067870
+:1057A000B01C1F2803DC2079801C1F2802DD0C2080
+:1057B00007B0F0BD1F20801B801E85B20295B11C72
+:1057C000781802A97DDF03900398002801D0039880
+:1057D000EEE72068022808D168460089A84204DC68
+:1057E00009200190684605890BE008200190207986
+:1057F000002804D02079A84201DC257901E0684620
+:105800000589681CC2B23046711CCEB23A5430468B
+:10581000721CD6B20199395406980178A81C081850
+:10582000C1B2069801700020C2E7F8B505460C46E3
+:105830002078001D1F2801DD0C20F8BD684679DFA7
+:105840000646002E01D03046F7E703212278501C8F
+:105850002070A95419212278501C2070A954207856
+:1058600041196846008800F08AFA217840182070B3
+:105870000020E2E7F8B505460F4616461C46686864
+:10588000002801D10720F8BD2078801C2988401805
+:105890001F2801DD0C20F6E72878401CC1B22278D1
+:1058A000501C2070B1542178481C207077542A88ED
+:1058B00023789819696802F01DFF2078297840182C
+:1058C00020700020DFE730B503461078C01C1F2889
+:1058D00001DD0C2030BD02241578681C10704C5579
+:1058E0000A241578681C10704C551578681C1070C7
+:1058F0004B550020EEE7FFB587B006461D46109CCD
+:105900000020069020780590002747E0B9007068D5
+:1059100041180A886846028149884181002203A90A
+:1059200002A864DF04900498002802D004980BB009
+:10593000F0BD6846017B099881422ED10698002867
+:1059400001D0002000E00220019020786946097B08
+:105950004118019808181F2801DD0C20E7E7069878
+:10596000002809D12078401C20702278511C08980A
+:105970002170A854012006902078421903A902A89A
+:1059800064DF04900498002801D00498CFE72078C1
+:105990006946097B4018207000BF7F1C3088B842E0
+:1059A000B4DC0698002806D021780598401C081A17
+:1059B000C1B2059829540020B9E7FFB581B0074668
+:1059C00015460A9E009602223846049B0299FFF76C
+:1059D00092FF0446002C02D0204605B0F0BD009690
+:1059E000102229463846049BFFF785FF0446002C09
+:1059F00001D02046F1E70020EFE70146088806289D
+:105A000008DB08881922D201904205DD0888E04AA7
+:105A1000904201D0072070474888062808DB488854
+:105A20001922D201904205DD4888D94A904201D01E
+:105A30000720F0E70888D64A904208D0488890426C
+:105A400005D008884A88904201DD0720E3E700205E
+:105A5000E1E7F8B506460D4614462078801D1F285C
+:105A600001D90C20F8BD3046FFF7C7FF0746002FCD
+:105A700001D03846F6E705212278501C2070A95441
+:105A800012212278501C2070A95422785119308894
+:105A900000F075F92178401820702278511970882B
+:105AA00000F06DF92178401820700020DAE7F8B591
+:105AB00005460E4614462879801CC7B22078801C03
+:105AC000C0191F2801DD0C20F8BD781CC1B2227856
+:105AD000501C2070B154FF212278501C2070B1540A
+:105AE00022789119288800F04AF92178401820700E
+:105AF000A88800280EDDA868002801D10720E3E768
+:105B0000AA8823789819A96802F0F4FD20782979E9
+:105B1000401820700020D7E7FEB507460E46144611
+:105B2000786B002801D10720FEBD0020019032E0F3
+:105B3000796B0C22019850430D182879801CC0B253
+:105B400000900098401CC1B22278501C2070B154C3
+:105B500016212278501C2070B1542278911928887F
+:105B600000F00DF9217840182070A88800280EDD7B
+:105B7000A868002801D10720D6E7AA8823789819B9
+:105B8000A96802F0B7FD20782979401820700198A3
+:105B9000401CC0B201903820C15D01988142C7DC31
+:105BA0000020C1E7F8B504460F4616460025002040
+:105BB00030702068002809D0324639462046FFF769
+:105BC000E9FD0546002D01D02846F8BD6079002882
+:105BD00008D031463846FFF728FE0546002D01D093
+:105BE0002846F2E7208900280BDD33463A4601219A
+:105BF00020460830FFF73EFE0546002D01D028461E
+:105C0000E3E7206900280BD00020216908563246BE
+:105C10003946FFF758FE0546002D01D02846D4E747
+:105C2000A08A00280CDD3B460622022120461430C3
+:105C30000096FFF7C2FE0546002D01D02846C4E7B6
+:105C4000A08B00280CDD3B460722032120461C3098
+:105C50000096FFF7B2FE0546002D01D02846B4E7B6
+:105C6000A08C00280CDD3B46152214212046243050
+:105C70000096FFF7A2FE0546002D01D02846A4E7B6
+:105C8000E06A002809D032463946E06AFFF7E1FEB3
+:105C90000546002D01D0284697E7206B002809D043
+:105CA00032463946206BFFF702FF0546002D01D032
+:105CB00028468AE73820005D002809DD324639464B
+:105CC0002046FFF729FF0546002D01D028467CE736
+:105CD00028467AE701460889002807D0C8680028C6
+:105CE00004D0C86800780422104201D10720704710
+:105CF0000020FCE701460889002801DD07207047E5
+:105D00000020FCE7F3B593B00D46002112911191EC
+:105D10001398002814D01398FFF7DCFF0446002CDA
+:105D200002D0204615B0F0BD12AA09A91398FFF7BA
+:105D300039FF0446002C01D02046F3E709AE00E00D
+:105D40000026002D13D02846FFF7D4FF0446002C70
+:105D500001D02046E6E711AA01A92846FFF722FF55
+:105D60000446002C01D02046DCE701AF00E000270C
+:105D700010A803793A46017A304672DFD2E702462C
+:105D80000A70FF2000021040001248700220704785
+:105D9000FFFF0000F8B505460E4600243278214684
+:105DA000641C6A5472782146641C6A5400BF022C39
+:105DB00001D1012000E000200746002F05D100BFDF
+:105DC00008A22021FEF798FA00BF00BF2046F8BDC8
+:105DD00070B504460D46284602F06AFC20806560D6
+:105DE00070BD00002E2E5C2E2E5C2E2E5C2E2E5CA6
+:105DF0002E2E5C536F757263655C626C655C626CC1
+:105E0000655F73657276696365735C626C655F7309
+:105E100072765F636F6D6D6F6E2E63000A7B002A72
+:105E200004D04A6802600022027103E00A6802603E
+:105E30000122027170470246107900280FD048688D
+:105E40001368C01A8B68984204D910688B68C01810
+:105E5000106013E048681060002010710EE01068B8
+:105E60000B68C01A8B68984204D910688B68C01AF6
+:105E7000106003E00868106001201071106870471E
+:105E800010B5002819DAFE4A03071B0F083B9B08D0
+:105E90009B00D2588307DC0EFF23A3409A438B0755
+:105EA0001B0E8407E40EA3401A43F54B0407240F8E
+:105EB000083CA408A4001A5118E0F24A03231B026C
+:105EC000D21883089B00D2588307DC0EFF23A3401F
+:105ED0009A438B071B0E8407E40EA3401A43E94B39
+:105EE000032424021B198408A4001A5110BD10B504
+:105EF0000446E548846003211120FFF7C1FF10BD6F
+:105F000010B501200004E1494860E049403948608B
+:105F1000112000F0D4FC112000F0C3FC0120DC496A
+:105F200008602F20FEF720F910BD10B511200121C7
+:105F30008140D44A8032116000BF01200004D3495F
+:105F40008860D249403988600120D14948602F20BB
+:105F5000FEF70AF910BDF0B5024628205043CD4B9C
+:105F60001B68C118CC480068401C02D1CA480260B6
+:105F700057E0C9488B68006828246043C54C2468F2
+:105F800000198068834217D8C34800682823584303
+:105F9000C04B1B68C01880688B68C31ABE48006875
+:105FA00028246043BB4C246800198360BA48006809
+:105FB0004862B948026034E08B68B74D2C682868A5
+:105FC0000DE028254543B34E3668AD19AD685B1B1F
+:105FD000044628254543AF4E3668AD19686A451C0E
+:105FE00007D028254543AB4E3668AD19AD689D42B4
+:105FF000E7D3451C0CD028254543A64E3668AD197D
+:10600000AD68EE1A28254543A24F3F68ED19AE60F2
+:106010008B604862282565439E4E3668AD196A62DA
+:1060200000BFF0BD70B502469B4C2168084609E0F0
+:10603000904200D108E0014628244443954D2D6844
+:106040006419606A441CF3D100BF441C00D170BDC8
+:10605000814209D1904C246828256C438D4D2D68D0
+:106060006419646A8C4D2C6028244443894D2D6842
+:106070006419A36828244443864D2D686419656A11
+:1060800028244C43834E3668A419656228244C4367
+:10609000804D2D686419606A441C0CD02824444348
+:1060A0007C4D2D686419A468E51828244443794E72
+:1060B0003668A419A56000BFC9E710B5112000F02B
+:1060C000F7FB10BD10B5142000F0F2FB10BD70B549
+:1060D000044672480068002813D0704A216AE069BB
+:1060E00012689047054600BF2E46002E07D000BF1D
+:1060F0006BA2FF2155313046FEF7FEF800BF00BF0E
+:1061000002E0E169206A884770BDF8B5624800681E
+:10611000401C39D0002700F004FC04466C4801689C
+:10612000204600F001FC06465B48056811E0282087
+:106130006843584909684418A068B04200D90AE089
+:10614000A068361AA068C719656A2046FFF7BFFF26
+:1061500000BF681CEBD100BF5E4800785E4909783B
+:10616000884209D15C480078401CC0B25A49087086
+:10617000022801D100200870574800788000574954
+:106180000F50FFF79FFF00BFF8BD01465148007850
+:10619000514A127890421DD04E480078401C4D4A1A
+:1061A000107010460078022801D100201070494874
+:1061B00000788000494A10580860454A086812680B
+:1061C0008018434A1060104600680002000A106000
+:1061D000012070470020086000BFFAE7FEB52E4896
+:1061E000006801903E48007800903FE00098C000B1
+:1061F0003C4909684418257834E0182068436168F0
+:106200000F18681CC5B2A078A84200D100253868D4
+:10621000022802D0032822D10EE0282178684843C2
+:106220001C4909684618307E002804D07868FFF7BA
+:10623000F9FE0020307613E00CE0174909682822A7
+:106240005143144A1268881800210176124A416AA3
+:10625000116000BF10480068401CEED100E000BF94
+:1062600000BF00BF6078A842C7D100BF0098411EA0
+:10627000C9B200910028B9D107480168019833E0FC
+:106280001CED00E000E100E000150140401301407A
+:1062900000100140802000208C200020A020002041
+:1062A0002E2E5C2E2E5C2E2E5C2E2E5C2E2E5C5303
+:1062B0006F757263655C6170705F636F6D6D6F6E3B
+:1062C0005C6170705F74696D65722E630000000020
+:1062D000902000209C2000209D2000209420002061
+:1062E0008520002088200020814201D00120FEBDB1
+:1062F0000020FCE7F0B50346002426E0FE4E366899
+:1063000028277E43FD4F3F68F01986689E4203D9D7
+:106310008668F61A86601CE086689B1B86683419BE
+:10632000002686600676F44E3568F34F466A3E6076
+:106330004669002E08D00E193602360AC660466934
+:10634000066116684662156000BFEB480068401C95
+:10635000D4D100BFF0BDFEB50746E7480068029003
+:10636000E7480078019069E00198C000E5490968B4
+:1063700045185CE0781C07D03E4628207043DF4972
+:1063800009684418676A23E02978182359436A6822
+:1063900050182978491C29702978AA78914201D18E
+:1063A00000212970466828217143D44A12688C184C
+:1063B0000168012902D1217E002900D037E08168DF
+:1063C000E160C1682161016961614169216200BFC9
+:1063D000CD49E0680968401A0002000ACB498842AA
+:1063E00008D2C949E068096800F09EFA216940189E
+:1063F000A06011E0C448E168006800F095FA0090E0
+:1064000021690098814204D921690098081AA06086
+:1064100001E00020A06000BF0020E06020610120BA
+:106420002076801E60623046FFF795FD00BF781C25
+:10643000A0D12878697888429CD100BF0198411E7C
+:10644000C9B2019100288FD1AB48016802988142FE
+:1064500001D00120FEBD0020FCE7FEB50546A648A0
+:106460000068401C34D0A448006828214843A34950
+:1064700009684018866800F054FA0290A24804683F
+:106480002146029800F050FAC01C0190681C01D10E
+:10649000FFF736FD0198B04201D2304600E0019886
+:1064A00004192402240A20469949086000BF00F01C
+:1064B00038FA00900299009800F036FAC71C20467E
+:1064C000029900F031FA874201D9FFF7F6FD01E0A9
+:1064D000FFF72BFDFEBDFEB50020C04301908A48AA
+:1064E0000468854807686846FFF74FFE0546FFF7D2
+:1064F00075FE0646002D05D001AA21460098FFF73B
+:10650000F9FE01260198FFF726FF002800D001269A
+:10651000002E02D03846FFF7A0FFFEBD4170704745
+:1065200070B502460B465078411C9078884200D1E5
+:1065300000211078884201D1002070BD1960507888
+:106540001826704355682C182046F6E7FFB581B031
+:1065500005460E461746E9006A4A126888186946D9
+:10656000FFF7DEFF0446002C02D1042005B0F0BD89
+:1065700001202060666000F0D4F9A060E760049814
+:1065800020610A986061E9005E4A126888180099E3
+:10659000FFF7C4FFFFF796FD0020E7E7F8B50446D4
+:1065A0000E46E100574A126888186946FFF7B8FF9F
+:1065B0000546002D01D10420F8BD022028606E6040
+:1065C000E100504A126888180099FFF7A7FFFFF70B
+:1065D00079FD0020F0E738B50446E100494A126829
+:1065E00088186946FFF79CFF0546002D01D104205D
+:1065F00038BD03202860001F6860E100414A12682E
+:1066000088180099FFF78AFFFFF75CFD0020EFE78D
+:1066100010B500203F49086000213E48416081607C
+:10662000C16000203B49403908604860FFF76DFDBC
+:1066300010BD10B5FFF74FFF10BDFFB581B00E467E
+:1066400017461C4620468107890F01D1012100E031
+:106650000021002902D1072005B0F0BD002C01D196
+:106660000720F9E7FFF761FC2B490A9808602B48DF
+:1066700006702248046000250CE0002128206843B1
+:106680001E4A12681150282068431C4A126880185C
+:1066900001766D1CB542F0DB2820704304190320FD
+:1066A0001749087017480460183400250CE0E90009
+:1066B000144A126888180021017041708770446084
+:1066C000182179430C196D1C032DF0DB0020C04309
+:1066D0000949086000201249087012490870142006
+:1066E00000F0EDF803211420FFF7CAFB142000F09E
+:1066F000D8F8019818E000008C20002080200020AD
+:10670000852000208820002090200020FFFF7F00AF
+:106710004015014040110140A020002084200020AD
+:106720009C2000209D200020FFF7E1FB00F0F9F8FD
+:106730008049086000208FE770B503460C467E480C
+:106740000068002801D1082070BD002A01D107206F
+:10675000FAE7002B01D10720F6E700211CE02820F2
+:106760004843754D2D682858002814D1012528204C
+:106770004843714E36683550282048436E4D2D6889
+:1067800040194460282048436B4D2D684019C26170
+:1067900019600020D8E7491C684800788142DEDB98
+:1067A0000420D1E710B500F092F8012802D00328A8
+:1067B00004D101E0002403E0012401E0022400BF31
+:1067C00000BF204610BDFEB504460D4616465A4889
+:1067D0000068002801D10820FEBD58480078844296
+:1067E00001D2052D01D20720F6E728206043524947
+:1067F00009680858012801D00820EDE728206043E7
+:106800004D49096840184068012801D1284600E038
+:1068100000200746FFF7C6FF3B462A462146009662
+:106820000190FFF793FED7E770B504464248006831
+:10683000002801D1082070BD40480078844201D36F
+:106840000720F8E7282060433B49096808580128D9
+:1068500001D00820EFE7FFF7A5FF05462146FFF727
+:106860009DFEE8E710B534480068002801D10820F3
+:1068700010BDFFF797FF0446FFF7ADFEF8E710B530
+:10688000044600F04EF82060002010BD70B50546AB
+:106890000E4614463146284600F046F82060002097
+:1068A00070BDC206D20E01219140254A1160704789
+:1068B000C206D20E01219140224A11607047C206E1
+:1068C000D20E012191401F4A80321160704710B5ED
+:1068D0001D484068C105C90D002920D00A46103A5C
+:1068E000104600280DDA184B1C330407240F083C0F
+:1068F000A408A4001B598407E40EE3401B069B0F69
+:106900000BE00F4B032424021B198408A4001B591D
+:106910008407E40EE3401B069B0FD8B210BD042091
+:10692000FCE70A48406870470246501A0002000A15
+:10693000704700009020002080200020842000204C
+:1069400000E100E000E200E000ED00E000150140A1
+:1069500070B506460C46154629462046FDF7DAFC7A
+:1069600070BDF8B559480078002800D1F8BD5848E6
+:106970000068002801D1012000E0002005465548AC
+:106980000068002801D1012000E00020064600BF79
+:10699000002D14D168464EDF0446052C01D1012597
+:1069A0000CE0002C06D000BF4BA26F212046FDF763
+:1069B000A3FC03E0464900980968884700BF002E01
+:1069C0001AD153480088009069465248006860DF39
+:1069D0000446052C01D101260DE0002C06D000BF95
+:1069E0003DA286212046FDF787FC04E04948006867
+:1069F00038490968884700BF002D02D0002E00D01A
+:106A000000E0C5E700BF00BFB0E7FFB581B00C46AE
+:106A100016461F46002C02D1072005B0F0BD2046C7
+:106A20008107890F01D1012100E00021002901D156
+:106A30000720F2E73748046035480680364807608B
+:106A40003649019810DF0546002D01D02846E4E7BD
+:106A500001201E490870162023DFDEE710B511DF84
+:106A60000446002C01D0012000E0002017490870E6
+:106A7000204610BD0146002901D10E20704715485F
+:106A800001600020FAE70146002901D10E2070477D
+:106A90000F4801600020FAE770B51F480068002821
+:106AA00011D01D4800688047044600BF2546002DD0
+:106AB00007D000BF08A2FF2139312846FDF71CFC92
+:106AC00000BF00BF01E0FFF74CFF70BDA420002015
+:106AD000B8200020B42000202E2E5C2E2E5C2E2EFE
+:106AE0005C2E2E5C2E2E5C536F757263655C736436
+:106AF0005F636F6D6D6F6E5C736F667464657669EE
+:106B000063655F68616E646C65722E6300000000EF
+:106B1000B0200020AC200020A820002051690100F6
+:106B200001464888B74A1288904206DB4888B54A31
+:106B30005288904201DC012070470020FCE738B504
+:106B40000546B1480088B14988423BD0B04800783A
+:106B5000401CAF490870AF48007B097888420FDBC2
+:106B6000A849A948008875DF0446002C07D0A94829
+:106B70008069002803D0A74881692046884721E022
+:106B80000020A3490870A348007C00280FD03B21B7
+:106B90009D48008876DF0446002C07D09D48806918
+:106BA000002803D09B4881692046884700BF994848
+:106BB0004069002806D00020009096484169684648
+:106BC000884700BF38BD70B505461C22294691484C
+:106BD000FDF7DAFA002090490870286800280BD0E9
+:106BE00008228848296801F085FD86487ADF044636
+:106BF000002C08D0204670BD82487BDF0446002C64
+:106C000001D02046F7E70020C0437F4908800020DC
+:106C10007F490870814A00218148FFF78DFDEAE72E
+:106C200010B57F480068FFF7FFFD10BD38B57D48FF
+:106C3000FFF776FF002829D17748007800280AD08E
+:106C400074484069002820D00020009071484169B4
+:106C50006846884719E06E480078002802D16D48E0
+:106C6000456801E06B488568002229466C48006849
+:106C7000FFF7A9FD0446002C07D06648806900286C
+:106C800003D064488169204688470AE0614840692A
+:106C9000002806D0012000905E4841696846884778
+:106CA00000BF00205C49087038BD10B50446A088BC
+:106CB00055490880082221460E315A4801F01AFD34
+:106CC0000020534908705348C089002801D1FFF7BC
+:106CD000ADFF10BD70B505460020C0434A4908808D
+:106CE00000204B4908704E480068FFF79DFD0446A0
+:106CF000002C07D047488069002803D045488169A7
+:106D00002046884770BD70B50546AC1D20884149B6
+:106D1000C989884220D1208B02281DD100BF00BF25
+:106D2000A17EE27E120211430846C107C90F002965
+:106D300002D0FFF77BFF0FE039480068FFF774FDD2
+:106D40000646002E07D033488069002803D031481A
+:106D500081693046884700BF70BD10B504460822DF
+:106D6000A11D304801F0C6FCFFF760FF10BD10B553
+:106D700004462088102806D0112808D012280ED0EA
+:106D8000502810D107E02046FFF78FFF0CE0204687
+:106D9000FFF7A0FF08E02046FFF7B5FF04E020461C
+:106DA000FFF7DBFF00E000BF00BF10BD38B50446B1
+:106DB00008222146134801F09DFC12487ADF05465F
+:106DC000002D1CD11748FFF7ABFE00280BD1012086
+:106DD000114908700B490C48008875DF05460120F1
+:106DE0000B4908700BE00B484069002806D00120D1
+:106DF0000090084841696846884700BF002528463A
+:106E000038BD0000BE200020C8200020FFFF000089
+:106E1000C620002020240020BC2000203F6B010061
+:106E2000D4200020CA2000200246100C0004F94B98
+:106E3000984202D10A8000207047501C01D10520E1
+:106E4000FAE70B20F8E77CB50546F34800780A28F6
+:106E500001DB04207CBDF0480078002806D1002228
+:106E60001146104601F0D4FBEC4908806A46EA4816
+:106E70000178EB4801F07AFA0446002C01D0204654
+:106E8000E8E76A46E4480178E54801F06FFA04460D
+:106E9000002C01D02046DDE704234C222946684619
+:106EA00001F0A8FA0446002C01D02046D2E7DB4AC4
+:106EB0004C21284601F0ACFBD849088008460088E0
+:106EC000D4490843D44909788900D64A5050D24859
+:106ED00000788000811800230422684601F08AFAB5
+:106EE0000446002C01D02046B4E7CB480078401C73
+:106EF000C94908700020ADE77CB50546CA4800784E
+:106F00000A2801DB04207CBDC7480078002806D190
+:106F100000221146104601F07BFBC44908806A46F6
+:106F2000C1480178C24801F021FA0446002C01D082
+:106F30002046E8E704231C222946684601F05AFA55
+:106F40000446002C01D02046DDE7B84A1C21284623
+:106F500001F05EFBB549088008460088AD4908434A
+:106F6000B14909788900B34A5050AF480078800091
+:106F7000811800230422684601F03CFA0446002CE4
+:106F800001D02046BFE7A8480078401CA6490870F9
+:106F90000020B8E77FB505469F480078002806D155
+:106FA00000221146104601F033FB9C49088002AADA
+:106FB000994801789A4801F0D9F90446002C02D08A
+:106FC000204604B070BD0023042202A901A801F0EC
+:106FD0006FFA0446002C01D02046F2E70023042279
+:106FE00002A901A801F064FA0446002C01D0204651
+:106FF000E7E769460198FFF717FF0446002C01D028
+:107000002046DEE704234C2202A9284601F050FA6C
+:107010000446002C01D02046D3E704234C2202A9C9
+:10702000284601F045FA0446002C01D02046C8E766
+:107030007A4A4C21284601F0EBFA78490880084644
+:10704000008869460988884206D173480078401C48
+:10705000714908700020B4E70B20B2E77FB5054600
+:1070600071480078002806D100221146104601F030
+:10707000CFFA6E49088002AA6B4801786C4801F08B
+:1070800075F90446002C02D0204604B070BD02AA57
+:1070900065480178664801F069F90446002C01D082
+:1070A0002046F2E70023042202A901A801F000FA19
+:1070B0000446002C01D02046E7E70023042202A961
+:1070C00001A801F0F5F90446002C01D02046DCE7C8
+:1070D00069460198FFF7A8FE0446002C01D020461F
+:1070E000D3E704231C2202A9284601F0E1F9044653
+:1070F000002C01D02046C8E704231C2202A9284600
+:1071000001F0D6F90446002C01D02046BDE7474ADD
+:107110001C21284601F07CFA444908800846008872
+:1071200069460988884206D13F480078401C3E499C
+:1071300008700020A9E70B20A7E710B50A213848FE
+:1071400001F014FA0446002C04D10A21384801F059
+:107150000DFA0446204610BD38B5002211461046EF
+:1071600001F056FA0090002409E068216143324A98
+:1071700088186A464C2101F04BFA0090641C2F4895
+:1071800000788442F1DB68460088244909888842F7
+:1071900001D0012038BD0020FCE738B5002211469F
+:1071A000104601F035FA009000240BE0682161439D
+:1071B000214A891808464C306A461C2101F028FAF9
+:1071C0000090641C1D4800788442EFDB684600880C
+:1071D00016490988884201D0012038BD0020FCE70B
+:1071E00070B505461648406E002808D01448406E19
+:1071F000142801D9032070BD114C503400E0002444
+:107200000F484C30008B82B221460E480088A9DF1F
+:10721000F1E7000000002453F4200020E0200020CB
+:10722000E4200020B0260020F5200020E2200020ED
+:10723000EC200020C0260020D0240020DC200020EC
+:1072400068240020DA20002010B500210020FE4A2A
+:107250001070FE4A107032E068224A43FC4BD0188E
+:10726000427A5207D20F12D0027852B2F74B1B78F3
+:10727000DB00F84CE25402461E32F44B1B78DB0074
+:107280001B195A60F14A1278521CF04B1A704222B4
+:10729000125C022A12D0027852B2EB4B1B78DB0050
+:1072A000ED4CE25402464232E74B1B78DB001B19DF
+:1072B0005A60E54A1278521CE34B1A70491CE748A1
+:1072C00000788142C8DB10BD7CB50546E3480078F4
+:1072D000042801DB04207CBD32222946E04801F06D
+:1072E00009FAE988DE48001FC18700214166DB48B2
+:1072F0000078DB49091F0860D8490978D74A127815
+:10730000521CD64B1A7068225143D14A8818682201
+:10731000D349091FFCF738FFFFF796FF0120D14939
+:10732000087000221146D0480088A9DF0446002CCE
+:1073300001D02046CFE7CA48001FFFF784FD04466E
+:10734000042C15D1C9484068002811D004200090B1
+:10735000C348001F007841B268460171C048001F51
+:10736000C18F6846C180C14841686846884703E0CC
+:10737000002C01D02046AEE7BC484068002811D060
+:1073800000200090B648001F007841B268460171A5
+:10739000B348001FC18F6846C180B4484168684641
+:1073A000884700BF002096E770B5AD48001F04680D
+:1073B000002C23DBA948007884421FDAA948007812
+:1073C000002808D068216143A14A88184C22A449AA
+:1073D000091FFCF7D9FEA648007800280AD06821CA
+:1073E00061439B4A891808464C301C229C4948310D
+:1073F000FCF7CAFEFFF728FF002500E0032528461A
+:1074000070BD70B50546002412E0682060439049C5
+:107410004018C08FA8420AD1682060430A468118EC
+:1074200068228F48001FFCF7AFFE002070BD641C6F
+:107430008A4800788442E8DB0520F7E770B594B00D
+:107440000020864908702FE001A8FFF7A3FD05463C
+:10745000052D00D12CE0002D02D0284614B070BDBF
+:10746000019E7E480078864201DD0B20F6E7682108
+:107470007143774A88184C2201A9FCF785FE7748AA
+:10748000007886420FD1002168207043704A80182E
+:107490004166491E682070438018C1646F480078B7
+:1074A000401C6E49087000BF6C4800780428CBDB94
+:1074B00000BF00241DE00DA8FFF7D0FD0546052DF7
+:1074C00000D11AE0002D01D02846C7E763490978AA
+:1074D0000D98884201DD0B20C0E768220D995143C9
+:1074E0005B4A891808464C301C220DA9FCF74CFE5B
+:1074F000641C5A4800788442DDDB00BF574804789A
+:1075000010E00021C94368206043514A1150002116
+:107510006820604380184166491E682060438018D7
+:10752000C164641C042CECDBFFF78EFE002095E7A1
+:107530007CB50446A0884C4908800020C0434849D7
+:10754000091F08600722A11D45483E3001F0D2F80E
+:1075500000214348001F4166607BC007C00F1AD05E
+:10756000607B4508042D04DAE8003A4908560428EF
+:1075700004DB3E488168032088470BE0E800354A79
+:10758000105668225043324A811868223448001F3E
+:10759000FCF7FAFD21E0002519E0E8002E4940182B
+:1075A000466806222E493F31701C01F053F800282E
+:1075B0000CD1E800284A105668225043244A81180A
+:1075C00068222748001FFCF7DFFD04E06D1C1E4801
+:1075D00000788542E1DB00BF00BF2148001F006842
+:1075E000401C1AD000201F490870214908701F480C
+:1075F0004068002811D0012000901948001F007831
+:1076000041B2684601711648001FC18F6846C180AB
+:10761000164841686846884700BF7CBD70B505467E
+:107620000F48001F0068401C0AD012480078C007AD
+:10763000C00F05D00F4800780221084040081FD035
+:1076400000221146084817E0DD200020DE2000203F
+:10765000D02400209026002070260020DC2000206E
+:107660006C240020F7200020DA2000205C24002079
+:10767000F8200020F62000200088A9DF044608E05A
+:10768000FE48FFF7ADFD0446002C02D10120FC4965
+:107690000870002C03D0FB4881682046884770BDE5
+:1076A0007CB5044632222146F448001D01F022F840
+:1076B000E188F248C1870146096868225143F24ACD
+:1076C0008818682101F075F8EC490968682251436F
+:1076D000ED4A88186822E949FCF756FDE748FFF7AC
+:1076E000B2FB0546042D13D1E648406800280FD0B0
+:1076F00004200090E148007841B268460171DF48FB
+:10770000C18F6846C180DF4841686846884705E008
+:10771000002D03D0DB48816828468847D948406857
+:1077200000280FD003200090D448007841B268466A
+:107730000171D248C18F6846C180D248416868460D
+:10774000884700BF7CBD70B504462078002800D073
+:1077500070BDCE480078C007C00F00D1F8E7C74819
+:107760000068401C12D1E088FFF74BFE0546002D53
+:1077700001D10F2503E02046FFF7A6FD0546002DA9
+:1077800003D0C04881682846884705E00120C049E9
+:1077900008702046FFF784FF00BFD9E770B50546A3
+:1077A000E889FFF72EFE0446002C10D10120B849CD
+:1077B00008700022B1490A31B648008881DF0446CA
+:1077C000002C1ED0AF4881682046884719E0052C60
+:1077D00017D10C22A91DA948363000F08BFF0022DA
+:1077E0001146AC48008881DF0446002C03D0A54830
+:1077F00081682046884700221146A6480088A9DFF4
+:107800000446002C03D09F4881682046884770BDFD
+:10781000FEB5044620780607360F20780709012EAA
+:1078200001D1012F03DC022E2CD1002F2AD000BF62
+:1078300096480078012108439449087000BF8F489A
+:10784000FFF7CEFC0546002D04D08E4881682846FF
+:10785000884702E001208A4908708A484068002869
+:107860000FD0022000908548007841B2684601712F
+:107870008248C18F6846C18082484168684688470F
+:1078800000BF00BFFEBD10B50446A078C007C00F02
+:1078900012D000BF7D480078022108437B49087060
+:1078A00000BF76480068401C06D00021734841663E
+:1078B0001421503000F07DFF10BD10B5044676480D
+:1078C0000078002803D16F48816808208847208805
+:1078D000142820D006DC10280BD011280DD0132836
+:1078E00025D11CE0172812D018281CD052281ED1F0
+:1078F00009E02046FFF71CFE1AE000BF00206349A4
+:10790000087000BF14E02046FFF788FE10E0A01DBD
+:10791000FFF719FF0CE02046FFF740FF08E0A01D2D
+:10792000FFF7B1FF04E0A01DFFF772FF00E000BF0A
+:1079300000BF10BD38B500BF57480078002801D1FE
+:10794000082038BD00BF4D480068401C1CD01420E2
+:1079500000906A46494950314E480088AADF0446E3
+:10796000002C01D02046ECE7444800684349C86435
+:107970006846018841484166FFF716FD0446002C21
+:1079800001D02046DDE700BFFFF7E6FB00281DD051
+:107990000A21424800F0EAFD0446002C01D02046AE
+:1079A000CFE700203E49087000250BE068216943BD
+:1079B000354A8818FFF747FA0446002C01D02046C4
+:1079C000BFE76D1C374800788542EFDBFFF7E5FB2A
+:1079D00000281FD00A21344800F0C8FD0446002CBE
+:1079E00001D02046ADE700203049087000250DE0A9
+:1079F00068216943244A891808464C30FFF77CFA0D
+:107A00000446002C01D020469BE76D1C25480078D9
+:107A10008542EDDB0020C0431E49088017490860FD
+:107A2000C8640021154841660020194908701449AE
+:107A3000087000BF85E738B51048406E00283FD178
+:107A4000142000906A460D49503112480088AADF80
+:107A50000446002C01D0204638BD0848006807497C
+:107A6000C86468460188054841660146096868227D
+:107A70005143054A891808464C3019E06824002013
+:107A8000F82000205C240020D0240020F6200020D4
+:107A9000F7200020DA200020D8200020E420002059
+:107AA000F4200020DC200020EC200020F520002025
+:107AB0001C22DF49FCF768FBDD48FFF71DFACBE726
+:107AC0000820C9E710B500BFDA480078002801D1C6
+:107AD000082010BD00BF0020D7490870D7490870A2
+:107AE000D7490870FFF729FBF3E770B504460D4648
+:107AF000601C0DD0D0480078844209DA002D07D0F0
+:107B000068206043CF49401840308078022801D176
+:107B1000072070BD68206043CA4A80180146423180
+:107B20000722284600F0E6FD0020F2E710B50246E5
+:107B3000002107E0C800C44BC01840688B00C34C4C
+:107B4000E050491CC24800788142F3DB002107E085
+:107B5000C800C04BC01840688B00BF4CE050491CA7
+:107B6000BE4800788142F3DBBC4800781071BB4806
+:107B70000078002801D0B84800E000201060B44828
+:107B800000781073B2480078002801D0AF4800E0B8
+:107B900000209060002010BDF8B506460F46144640
+:107BA000069D002C03D0AE48816820468847F8BD6A
+:107BB0003EB50546A868002801D107203EBD5020EB
+:107BC00001900A200290A7480090A749684600F05B
+:107BD00026FB0446002C01D02046EFE72020019030
+:107BE000A249684600F01BFB0446002C01D0204649
+:107BF000E4E79B4829460EC90EC068218C484C38E2
+:107C000000F0D7FD0020C04389494C3908609849ED
+:107C100008800020884908708849087088490870E1
+:107C200000BF9449087000BF8D48807800281FD09D
+:107C3000FFF783FA0446002C01D02046BEE700205F
+:107C40007D490870087810E00022D2436821414342
+:107C50007C4B5A50002268214143C9184A66521E83
+:107C600068214143C918CA64401C0428ECDB06E0C3
+:107C7000FFF7E4FB0446002C01D020469EE70120DC
+:107C80006C490870002099E710B5024600BF6948AA
+:107C90000078002801D1082010BD00BF002901D1C3
+:107CA0000E20F9E70888644B1B78984201DA0C2013
+:107CB000F2E7614800780880002A01D10020EBE754
+:107CC000002007E0682343435E4C1B19DB8F440010
+:107CD0001353401C584B1B789842F3DB0020DBE722
+:107CE000F8B5074600BF53480078002801D10820A6
+:107CF000F8BD00BF0026F64300240AE06820604378
+:107D000050494018C08FB84201D166B205E0601CEE
+:107D1000C4B2494800788442F0DB00BF701C01D136
+:107D20000520E5E7F4B226E0621C68235A43454B80
+:107D3000D11868226243D0186822FCF725FA68201F
+:107D4000604340490858401E682161433D4A5050F5
+:107D50006820604311464018C06C411C09D068205F
+:107D6000604311464018C06C401E68216143891869
+:107D7000C864601CC4B230480078401EA042D3DC06
+:107D80002D490978491E682251432E4A88186821D6
+:107D900000F00FFD28480078401E27490870FFF7C3
+:107DA000CCF90546002D01D02846A1E70020234943
+:107DB00008702349087000240CE068216143214ABF
+:107DC0008818FFF740F80546002D01D028468FE7B8
+:107DD000601CC4B2184800788442EEDB00240EE038
+:107DE00068216143174A891808464C30FFF784F828
+:107DF0000546002D01D028467AE7601CC4B20E4823
+:107E000000788442ECDBFFF71FFA002070E70146A0
+:107E100000BF08480078002801D10820704700BF43
+:107E200014480078C007C00F08700020F6E7000073
+:107E3000B4240020D8200020DC200020F4200020E2
+:107E4000F5200020D0240020902600204C24002083
+:107E5000DE200020702600203C240020DD200020B1
+:107E60005C240020997B0100E4200020EC2000200D
+:107E7000DA200020F620002000231C214143FA4A8A
+:107E800053541C21414389184B6002231C21414358
+:107E900089188B6000231C2141438918CB601C2169
+:107EA000414389180B611C21414389184B611C21F6
+:107EB000414389188B61704710B50020EA49091FBA
+:107EC000087048708870002403E02046FFF7D4FF54
+:107ED000641C1E2CF9D310BDF8B50F25E248001F15
+:107EE00000781C214843E0494418E068009020785D
+:107EF000042810D18006006981B2009800F05BFC74
+:107F0000A1694618304650DF0546002D02D1A06910
+:107F1000401CA06126E02078022823D12769A069AF
+:107F20003F18A16960694118009808180090A1697C
+:107F30006068461A01208002864205D2B20839469E
+:107F4000009851DF054605E0FF22013239460098CE
+:107F500051DF0546002D04D101218902A069401896
+:107F6000A06100BF002D03D10120BF49091F887007
+:107F70002846F8BDFFB581B005460E4617460024D9
+:107F80000420B949091F8978002945D1B649091F3C
+:107F90004978002940D1B44A121F1170114609785E
+:107FA0001C225143B04A5554111F09781C225143D9
+:107FB000AD4A89180F61111F09781C225143AA4A42
+:107FC000891873683268CB608A60A74A121F1278DA
+:107FD0001C235A43A44BD218049951601A1F1278DB
+:107FE0001C235A43A04BD2180A99516100239E4981
+:107FF000091F09781C2251439B4A89188B61111F64
+:108000004978491C121F5170FFF766FF002801D004
+:10801000112838D1002036E09349091F49781E29DC
+:1080200031D09149091F09788F4A121F5278891857
+:10803000CCB21E2C02DB21461E39CCB21C2161437E
+:10804000894A55541C21614389180F611C216143E1
+:10805000891873683268CB608A601C226243824B45
+:10806000D218049951601C226243D2180A995161B6
+:1080700000221C216143C9188A61191F4978491CD3
+:108080001A1F5170002005B0F0BDF8B504467648BF
+:10809000001F00781C21484373490D5C081F0078BD
+:1080A0001C21484370494018806800016F490E58F0
+:1080B000042D01D1002007E06B48001F00781C212F
+:1080C00048436949401840686749091F009009788A
+:1080D0001C225143644A89180B69111F09781C221C
+:1080E0005143614A89180846083022462946B0475C
+:1080F000F8BD70B55C48001F00781C2148435A4900
+:1081000045186968A86988420FD35749091F08783C
+:10811000FFF7B2FE5448001F4078401E5249091F25
+:10812000487008460078401C087000244E48001F24
+:108130004078002809D0FFF7CFFE0446002C04D079
+:10814000112C02D02046FFF7A0FF204670BD70B56D
+:10815000054600264448001F8078012829D10020C8
+:108160004149091F8870022D02D0032D1ED119E04C
+:108170003D48001F00781C2148433B4944182078A3
+:10818000042803D16168A069884202D33046FFF712
+:108190007CFFFFF7AEFF0646002E02D03046FFF709
+:1081A00074FF04E00D20FFF770FF00E000BF00BF88
+:1081B00000BF70BD10B5FFF77FFE00202C4908609E
+:1081C0002C484069401C08D001221207126991B264
+:1081D000284A506900F0EFFA02E0012000074069E8
+:1081E000801E01210907096989B2484322490860B4
+:1081F00001202249087000200DE0002102011B4BE4
+:1082000099500201D21891600201D218D181020165
+:10821000D2189181401C0228EFD3002010BDF8B580
+:1082200004460D4600BF15480078002801D10820FB
+:10823000F8BD00BF002C01D10E20F9E7002D01D1BF
+:108240000E20F5E72068002801D10E20F0E701217B
+:1082500009076068096989B2884202D860681028F5
+:108260000ED20720E4E70000D42600201C2A0020BC
+:10827000002100200010001004210020FC2000201C
+:10828000A06800281CD0DD484069401C08D00122AD
+:108290001207126991B2D94A506900F08CFA02E0D3
+:1082A00001200007406901210907096989B2484393
+:1082B0006268A1685143D24A12688918884201D283
+:1082C0000720B5E7CF480068022801D10420AFE7B6
+:1082D000CC4800682860CA4800686860C949686876
+:1082E00009680901C84A89184860C64920680968B0
+:1082F00009015050C34960680968090189188860FC
+:108300002089C049096809018918888100276668A1
+:10831000A1684E4300BF781C87B2012000070069A6
+:1083200080B2B04205D201200007006980B2361A3F
+:1083300000E0002601200007006980B2B04909680A
+:108340004018AF49086001200007006980B2B042C0
+:10835000E1D9AC4800680001AB494018C781A94881
+:108360000068401CA7490860002061E73CB503464F
+:1083700000BFA6480078002801D108203CBD00BFFE
+:10838000002B01D10E20F9E7002A01D10E20F5E7DC
+:108390001868022805D2186800019B4C2058002854
+:1083A00001D10720EAE75C68186801940090019CFD
+:1083B00018680001944D40198068484320180190C6
+:1083C000009800012C4600198089009C2401641942
+:1083D000A4686043009C2401641964680019019C2E
+:1083E000A04201D80720C9E7019C009854601060A2
+:1083F0000020C3E7F8B504460D4616461F4600BFE9
+:1084000082480078002801D10820F8BD00BF002D67
+:1084100001D10E20F9E7002C01D10E20F5E72068EC
+:10842000022805D22068000177490858002801D1A8
+:108430000720EAE720680001734940188089216815
+:108440000901714A89188968484321680901891816
+:10845000496840186168884201D80720D5E7002E96
+:1084600006D020680001684940188068B04201D2F7
+:108470000720CAE7F01921680901634A8918896849
+:10848000884201D90720C0E7284600F0AEF800284E
+:1084900004D0E81900F0A9F8002801D11020B4E7B1
+:1084A00033462A46214602200097FFF763FDACE7DA
+:1084B000F8B507460C4615461E4600BF53480078DF
+:1084C000002801D10820F8BD00BF002C01D10E20EA
+:1084D000F9E7002F01D10E20F5E72068022805D228
+:1084E0002068000148490858002801D10720EAE720
+:1084F0002068000144494018808921680901424AE6
+:108500008918896848432168090189184968401811
+:108510006168884201D80720D5E7002D06D0206881
+:108520000001394940188068A84201D20720CAE7F3
+:10853000A81921680901344A89188968884201D933
+:108540000720C0E7384600F050F8002804D0B819DA
+:1085500000F04BF8002801D11020B4E760688119C1
+:108560002A46384600F0C6F80020ACE7F8B50446C5
+:108570000F4600BF25480078002801D10820F8BD2B
+:1085800000BF002C01D10E20F9E72068022805D297
+:10859000206800011C490858002801D10720EEE797
+:1085A0002068000118494018808921680901164A8D
+:1085B0008918896848432168090189184968401861
+:1085C0006168884201D80720D9E7206800010E4978
+:1085D0004018C689002033460246214600900420F8
+:1085E000FFF7C8FC05462846C9E701468807800F03
+:1085F00001D1012070470020FCE7000000100010AE
+:1086000004210020002100201C2A0020FC20002042
+:1086100070B503460C46002A01D10D4D00E01588C7
+:108620002846002110E0051206023543A8B25D5C21
+:10863000684005062D0F684005034540A8B20506B1
+:108640002D0D6D006840491CA142ECD370BD0000A7
+:10865000FFFF0000034610B50B439B070FD1042A10
+:108660000DD308C810C9121FA342F8D018BA21BAF6
+:10867000884201D9012010BD0020C04310BD002A4E
+:1086800003D0D30703D0521C07E0002010BD0378AD
+:108690000C78401C491C1B1B07D103780C78401C2C
+:1086A000491C1B1B01D1921EF1D1184610BD0000C0
+:1086B00030B5441C03E00178401C00290DD081072F
+:1086C000F9D10B4BDD0104C8D11A91432940FAD0EE
+:1086D000001B0A0603D0C01E30BD001B30BD0A04BB
+:1086E00001D0801E30BD0902FCD0401E30BD00000C
+:1086F00001010101F8B5042A2CD3830712D00B78AD
+:10870000491C0370401C521E83070BD00B78491C78
+:108710000370401C521E830704D00B78491C037061
+:10872000401C521E8B079B0F05D0C91ADF00202367
+:10873000DE1B08C90AE0FBF727FDF8BD1D4608C986
+:10874000FD401C46B4402C4310C0121F042AF5D231
+:10875000F308C91A521EF0D40B78491C0370401C50
+:10876000521EEAD40B78491C0370401C521EE4D4FC
+:1087700009780170F8BD01E004C0091F0429FBD28B
+:108780008B0701D50280801CC90700D0027070479A
+:1087900000290BD0C30702D00270401C491E0229D9
+:1087A00004D3830702D50280801C891EE3E70022E0
+:1087B000EEE70022DFE7002203098B422CD3030AF5
+:1087C0008B4211D300239C464EE003460B433CD41E
+:1087D000002243088B4231D303098B421CD3030A86
+:1087E0008B4201D394463FE0C3098B4201D3CB01B6
+:1087F000C01A524183098B4201D38B01C01A5241E6
+:1088000043098B4201D34B01C01A524103098B42E9
+:1088100001D30B01C01A5241C3088B4201D3CB00D4
+:10882000C01A524183088B4201D38B00C01A5241B7
+:1088300043088B4201D34B00C01A5241411A00D267
+:1088400001465241104670475DE0CA0F00D04942D0
+:10885000031000D34042534000229C4603098B4240
+:108860002DD3030A8B4212D3FC22890112BA030AC8
+:108870008B420CD3890192118B4208D3890192114A
+:108880008B4204D389013AD0921100E08909C309CF
+:108890008B4201D3CB01C01A524183098B4201D3D1
+:1088A0008B01C01A524143098B4201D34B01C01ABC
+:1088B000524103098B4201D30B01C01A5241C30834
+:1088C0008B4201D3CB00C01A524183088B4201D3A3
+:1088D0008B00C01A5241D9D243088B4201D34B00BE
+:1088E000C01A5241411A00D20146634652415B1000
+:1088F000104601D34042002B00D5494270476346E1
+:108900005B1000D3404201B50020C046C04602BD06
+:10891000704770477047754600F022F8AE46050074
+:1089200069465346C008C000854618B020B5FBF71D
+:1089300057FC60BC00274908B6460026C0C5C0C524
+:10894000C0C5C0C5C0C5C0C5C0C5C0C5403D490043
+:108950008D4670470446C046C0462046FBF7FDFBE7
+:10896000004870473C2A002001491820ABBEFEE7B2
+:108970002600020070470000141801000F180100C3
+:108980000A180100A489010000200020080100004D
+:1089900004410100AC8A0100082100209819000060
+:1089A000204101000024F400FFFF0000000000004F
+:1089B00000000000000000000000000000000000B7
+:1089C00000000000000000000000000000000000A7
+:1089D0000000000000000000000000000000000097
+:1089E0000000000000000000000000000000000087
+:1089F0000000000000000000000000000000000077
+:108A00000000000000000000000000000000000066
+:108A10000000000000000000000000000000000056
+:108A20000000000000000000000000000000000046
+:108A30000000000000000000000000000000000036
+:108A40000000000000000000000000000000000026
+:108A50000000000000000000000000000000000016
+:108A60000000000000000000000000000000000006
+:108A700000000000000000000000000000000000F6
+:108A800000000000000000000000000000000000E6
+:108A900000000000000000000000000000000000D6
+:0C8AA000000000000000000000000000CA
+:04000005000140C1F5
+:00000001FF
diff --git a/app/src/main/res/raw/ble_app_rscs_s110_v7_0_0.hex b/app/src/main/res/raw/ble_app_rscs_s110_v7_0_0.hex
new file mode 100644
index 000000000..247ec02d7
--- /dev/null
+++ b/app/src/main/res/raw/ble_app_rscs_s110_v7_0_0.hex
@@ -0,0 +1,1344 @@
+:020000040001F9
+:1060000058360020A1610100BB610100BD610100A3
+:106010000000000000000000000000000000000080
+:10602000000000000000000000000000BF6101004F
+:106030000000000000000000C1610100C361010018
+:10604000C5610100C5610100C5610100C5610100B4
+:10605000C561010000000000C5610100C5610100CB
+:10606000C5610100C5610100C5610100C561010094
+:10607000C5610100C5610100C5610100C561010084
+:10608000C561010083860100C5610100C561010091
+:10609000A5860100C56101001B8B0100C5610100DF
+:1060A000C5610100C56101000000000000000000A2
+:1060B00000000000000000000000000000000000E0
+:1060C00000F002F800F031F80CA030C808382418AD
+:1060D0002D18A246671EAB4654465D46AC4201D120
+:1060E00000F023F87E460F3E0FCCB6460126334221
+:1060F00000D0FB1AA246AB4633431847CC510000F0
+:10610000DC510000103A02D378C878C1FAD852079F
+:1061100001D330C830C101D504680C6070471FB589
+:10612000C046C0461FBD10B510BD05F04AF8114667
+:10613000FFF7F5FF00F099FD05F062F803B4FFF7F3
+:10614000F2FF03BC05F066F8401E00BF00BF00BFB1
+:1061500000BF00BF00BF00BF00BF00BF00BF00BF47
+:1061600000BFF1D17047000070B505460C461646D9
+:1061700002E00FCC0FC5103E102EFAD2082E02D32B
+:1061800003CC03C5083E042E07D301CC01C5361F3E
+:1061900003E021782970641C6D1C761EF9D270BD55
+:1061A00003210C4802680A4302600B4802680A4354
+:1061B00002600A4880470A480047FEE7FEE7FEE71C
+:1061C000FEE7FEE7FEE7000006480749074A084BDE
+:1061D000704700002405004054050040996201000A
+:1061E000C16001005826002058360020582E00209B
+:1061F000582E002032483349086070473248008CDE
+:10620000C0B2012812D13048808C0007000F00284E
+:106210000CD12D48806AF0210840402806D12A4838
+:10622000C06A0840002801D1012070470020FCE727
+:106230002548008CC0B201282CD12348808C00074F
+:10624000000F002826D12048806AF021084000284D
+:1062500006D11D48C06A0840002801D101207047BE
+:106260001948806AF0210840102806D11648C06AF3
+:106270000840002801D10120F1E71348806AF0218D
+:106280000840302806D11048C06A0840002801D1D3
+:106290000120E4E70020E2E710B5FFF7C9FF00287E
+:1062A00005D00A480A494860C8130A498861FFF7BF
+:1062B000A5FF002802D001200749886010BD00001A
+:1062C0000024F40000200020C00F00F0DFFF07C012
+:1062D00000050040006C00400006004030B513048B
+:1062E0008C0023430524240707252D02641985000B
+:1062F000635130BD0F2000F0DDFC00BFBFF34F8FB6
+:10630000FB48FC49C860BFF34F8F00BF00BFFEE7EA
+:1063100004460D462A462146F748FFF7EBFF70B5C5
+:10632000F649F74801F09DFDC5B22946F54801F050
+:106330002EF80446002C0DD0082C0BD0F2488442D5
+:1063400008D0F248844205D000BFF1A2C621204601
+:10635000FFF7D0FF70BD10B50446FFF7E0FF10BD9A
+:1063600010B504460120207000206070A070EB4939
+:10637000EB4801F076FDA080EA49EB4801F071FDA1
+:10638000A071EA49EA4801F06CFD2081A088032150
+:106390000902884201D90120A07010BD7FB50546D1
+:1063A0006846FFF7DDFF6946E24801F0CBF9044695
+:1063B000002C0ED0082C0CD0D348844209D0D348EE
+:1063C000844206D000BFD2A2FF210E312046FFF743
+:1063D00091FF7FBD10B5082000F063FC092000F09C
+:1063E00060FC0F2000F05DFC10BDF8B500BF002080
+:1063F000D14B05220321009002F058F9054600BF59
+:106400002E46002E06D000BFC1A2FF212831304603
+:10641000FFF770FF00BF00BFC84A0121C84802F063
+:10642000C2F9044600BF2546002D06D000BFB8A221
+:10643000FF212E312846FFF75DFF00BFC14A012131
+:10644000C14802F0B0F9044600BF2546002D06D031
+:1064500000BFAFA2FF2134312846FFF74BFF00BF3A
+:10646000F8BD3EB500BF6846007801090901491C26
+:10647000009168460078F02188431030009000BFFA
+:106480000A22B2A168467CDF044600BF2546002DE3
+:1064900006D000BF9EA2FF2148312846FFF72AFF01
+:1064A00000BF1120800178DF044600BF2546002D83
+:1064B00006D000BF96A2FF214B312846FFF71AFFF6
+:1064C00000BF002001900290FF2191316846818039
+:1064D0004900C18000210181FF219131418101A842
+:1064E0007ADF044600BF2546002D06D000BF88A2F3
+:1064F000FF2155312846FFF7FDFE00BF3EBD30B5F8
+:1065000093B006210491944A07CA01AB07C338210E
+:1065100005A804F0A4FD02216846017501218175DA
+:10652000018304A907910321818501A90C91002110
+:1065300005A801F0FDFB044600BF2546002D06D04E
+:1065400000BF73A2FF2176312846FFF7D3FE00BFBC
+:106550001421824804F083FD0020804908704860BF
+:10656000087228200882B420488213B030BD30B5AC
+:1065700099B000201690179018901690052010A939
+:10658000488400BF10A8007F01090901491C10A818
+:106590000177007FF0218843103010A9087700BFF1
+:1065A00000BF16A840790009000116A9487116A875
+:1065B0004079F021884316A9487100BF00BF16A892
+:1065C000807900090001887116A88079F02188433C
+:1065D00016A9887100BF00BF10A8C07F010909017A
+:1065E000491C10A8C177C07FF0218843103010A942
+:1065F000C87700BF00BF16A8007A0009000116A9DD
+:10660000087216A8007AF021884316A9087200BF04
+:10661000484801F069F8044600BF2546002D06D021
+:1066200000BF3BA2FF219F312846FFF763FE00BF5A
+:10663000142111A804F013FD00BF10A8407C01092B
+:106640000901491C10A84174407CF0218843103096
+:1066500010A9487400BF00BF11A8807B0009000189
+:10666000401C11A9887311A8807BF0218843103049
+:1066700011A9887300BF00BF11A8C07B00090001E9
+:10668000C87311A8C07BF021884311A9C87300BF4B
+:1066900000BF10A8007D01090901491C10A801755F
+:1066A000007DF0218843103010A9087500BF00203C
+:1066B0001190012110A8017200201390642110A8EC
+:1066C000017411A90F4800F041FE044600BF2546A1
+:1066D000002D06D000BF0EA2FF21B1312846FFF7E2
+:1066E00009FE00BF402101A804F0B9FC38E0000019
+:1066F0000400FA0500ED00E0EFBEADDE4021002011
+:10670000082000200C21002004300000013400008B
+:106710002E2E5C6D61696E2E6300000050210020FA
+:10672000102000206021002018200020702100206F
+:106730002020002024210020802100205763010018
+:10674000282000209D6301002C2000204E6F7264E1
+:1067500069635F525343000040B20100F8200020FB
+:10676000FEA101A801F04AFB00BF10A800780109B2
+:106770000901491C10A801700078F02188431030ED
+:1067800010A9087000BF00BF10A8407800090001E0
+:10679000487010A84078F021884310A9487000BFC5
+:1067A00001A800F0ABFB044600BF2546002D06D033
+:1067B00000BFEF4AFF21BC312846FFF79BFD00BF19
+:1067C00019B030BD10B55120EA4908606420486016
+:1067D000012088600873E84801F036FB8020E74913
+:1067E00008600D20C0014860FF20813088600020D3
+:1067F0000873E34801F028FB2820E2490860A02044
+:1068000048601420886000200873DF4801F01CFBFA
+:106810001420DE4908607D20486005208860002043
+:106820000873DB4801F010FB10BD70B50022012198
+:106830000904D848006801F0FDFF044600BF264661
+:10684000002E06D000BFCA4AFF21EC313046FFF7C8
+:1068500051FD00BF0125ED0300222946CE48006806
+:1068600001F0E8FF044600BF2646002E06D000BF18
+:10687000BF4AFF21F1313046FFF73CFD00BF70BD3C
+:1068800038B5684604F01DFB044600BF2546002DC0
+:1068900006D000BFB64AFF21FF312846FFF72AFD88
+:1068A00000BF0098002803D00120BC49087038BD03
+:1068B000BB4873DF044600BF2546002D05D000BF4E
+:1068C000AB4AB8492846FFF715FD00BF082000F085
+:1068D000F1F900BFEBE770B505462878002810D124
+:1068E0003B21B148008876DF044600BF2646002ED3
+:1068F00006D000BF9E4AAB4917313046FFF7FAFC7D
+:1069000000BF70BD044600BF994AA6492231204607
+:10691000FFF7F0FC30B587B01C21684604F09FFB00
+:10692000002000900520C00301900F200004029079
+:106930000321684601739D4841896846C181002151
+:1069400001749B4805909B480690684602F07DF9CB
+:10695000044600BF2546002D06D000BF844A092109
+:1069600089012846FFF7C6FC00BF07B030BD70B5EF
+:1069700004462088102804D011280CD0192829D1C9
+:1069800013E0092000F096F9082000F098F9A0889B
+:10699000854908801FE0092000F091F90020C043DC
+:1069A00081490880FFF76CFF15E0A079002810D11D
+:1069B000082000F084F934DF054600BF2E46002E83
+:1069C00006D000BF6A4A77495B313046FFF792FC38
+:1069D00000BF00E000BF00BF70BD10B50446022C30
+:1069E00002D0032C0BD100E000BF6C4800780028D7
+:1069F00004D0002069490870FFF742FF00E000BFA3
+:106A000000BF10BD10B50446204603F00CF9214626
+:106A1000664800F032FD2146674800F09AFB2046A8
+:106A200002F0E7F92046FFF7A2FF10BD10B50446BB
+:106A3000204603F0B2FE2046FFF7CFFF10BDF8B5A9
+:106A400000BF002350225D49082002F01EF80546D1
+:106A500000BF2E46002E06D000BF454AAB2189005C
+:106A60003046FFF747FC00BF00BF002000900090B9
+:106A7000684660DF044600BF2546002D06D000BFF3
+:106A80003B4A4849AC312846FFF734FC00BF4C482C
+:106A900002F031F8044600BF2546002D06D000BFA5
+:106AA000334A4049B0312846FFF724FC00BF45482F
+:106AB00002F02AF8044600BF2546002D06D000BF8C
+:106AC0002B4A3849B4312846FFF714FC00BFF8BD03
+:106AD00010B5032200210846FFF700FC0322002125
+:106AE0000120FFF7FBFB10BDF8B506460F46144624
+:106AF00000BF2546002D06D000BF1D4A2949D031D0
+:106B00002846FFF7F7FB00BF0020F8BD7FB503F074
+:106B1000E7FE044600BF2546002D06D000BF144AFC
+:106B20002049DF312846FFF7E5FB00BF0120264959
+:106B30000969C1400140002900D100E00020039014
+:106B400003A802F00DFC044600BF2546002D3FD0EF
+:106B500000BF074ABB218900284637E04E6F7264A8
+:106B6000696353656D69636F6E647563746F7200FA
+:106B70001067010040210020082000205021002043
+:106B8000102000206021002018200020702100200B
+:106B900020200020282000202C2000203120002050
+:106BA000F8200020070200000420002024210020FB
+:106BB000D7680100056901000C210020C0230020D6
+:106BC000056A01002D6A010000050050FFF792FBE5
+:106BD00000BF062101A8023004F03FFA1E216846DA
+:106BE000C180007A40084000401C6946087268462F
+:106BF000007A02218843694608726846007A1C219F
+:106C000088430C30694608726846007A2021884320
+:106C100069460872072168464172102181722A482C
+:106C200000900121684601716946284802F007FC7E
+:106C3000044600BF2546002D05D000BF244A254943
+:106C40002846FFF757FB00BF7FBD70B540DF044605
+:106C500000BF2546002D06D000BF1D4AC121890076
+:106C60002846FFF747FB00BF70BDFFF7B3FBFFF7F8
+:106C70002FFFFFF7E4FEFFF749FFFFF7B6FBFFF733
+:106C8000F0FBFFF73CFCFFF772FCFFF79BFDFFF703
+:106C900041FEFFF7CAFDFFF7F3FD00BFFFF7D5FF89
+:106CA000FCE703210522120707231B02D2188300E9
+:106CB000D150704701218140074A916070470121FE
+:106CC0008140054AD1607047E96A01003020002008
+:106CD00010670100FB0200000005005070B504467B
+:106CE0000D4600BF002D01D0012000E00020064627
+:106CF000002E05D100BFE6A23321FFF7FBFA00BF4B
+:106D000000BF00BF002C01D0012000E0002006469B
+:106D1000002E05D100BFDEA23421FFF7EBFA00BF41
+:106D200000BF2878207029882879FF231B0219408A
+:106D300000200206080A1043607028791B022968A7
+:106D4000194000200204080C1043A07029791B028E
+:106D50002868184000210902000E0843E0702879D5
+:106D60002071287A6071190CA8680840000AA07187
+:106D70000902A8680840000CE07170BDF8B505462E
+:106D80000E46002400BF002E01D0012000E00020AC
+:106D90000746002F05D100BFBDA24B21FFF7AAFA7D
+:106DA00000BF00BF00BF002D01D0012000E0002087
+:106DB0000746002F05D100BFB5A24C21FFF79AFA74
+:106DC00000BF00BF32782046611CCCB22A5429197A
+:106DD000708800F054F90019C4B22919B08800F085
+:106DE0004EF90019C4B22919F08800F048F90019C9
+:106DF000C4B200BF072C01D1012000E000200746EB
+:106E0000002F05D100BFA2A25421FFF773FA00BFE3
+:106E100000BFF8BDF0B58FB007460D4614461E46BC
+:106E200000BF002D01D0012000E00020009000985C
+:106E3000002805D100BF96A26D21FFF75BFA00BFC5
+:106E400000BF00BF002C01DD012000E00020009009
+:106E50000098002805D100BF8DA26E21FFF74AFAE5
+:106E600000BF00BF1C2107A804F0F9F86946087F9D
+:106E700002218843801C69460877002008900A9008
+:106E80000B900C900D9000BF012108A881766846F8
+:106E9000078700BF00200190317868460171717842
+:106EA0004171807906218843811C6846817180790F
+:106EB0000821884301466846817180791021884302
+:106EC00001466846817180794108490068468171B0
+:106ED000142102A804F0C3F80EA8029001A80390A0
+:106EE00068460482002141828482069502AA07A98D
+:106EF00074480088149BA2DF0FB0F0BD7FB5044634
+:106F000000BF0121684681736F49818100BF6D4ACE
+:106F100003A90120A0DF0546002D02D0284604B0B9
+:106F200070BD208800280DDD684800902288234627
+:106F30003C3367486168FFF76DFF0546002D01D0BF
+:106F40002846ECE7208900280EDD6248009022895F
+:106F500023463C335E48401FE168FFF75BFF054670
+:106F6000002D01D02846DAE7208A00280EDD5A4895
+:106F70000090228A23463C335548001F6169FFF781
+:106F800049FF0546002D01D02846C8E7208B002880
+:106F90000EDD52480090228B23463C334C48801E25
+:106FA000E169FFF737FF0546002D01D02846B6E717
+:106FB000208C00280EDD4A480090228C23463C336A
+:106FC0004348C01E616AFFF725FF0546002D01D02A
+:106FD0002846A4E7208D00280EDD42480090228D2F
+:106FE00023463C333A48401EE16AFFF713FF05464B
+:106FF000002D01D0284692E7206B002813D001A86D
+:10700000216BFFF76BFE384823463C33082201A969
+:1070100000902F48801FFFF7FDFE0546002D01D090
+:1070200028467CE700BF606B00280FD02F480090F7
+:10703000606B027923463C3301682548401CFFF70A
+:10704000E9FE0546002D01D0284668E7A06B002820
+:1070500013D001A8A16BFFF791FE254823463C33CE
+:10706000072201A900901A482730FFF7D3FE0546F2
+:10707000002D01D0284652E700BF00204FE702460E
+:107080000A70FF2000021040001248700220704772
+:107090002E2E5C2E2E5C2E2E5C2E2E5C2E2E5C5305
+:1070A0006F757263655C626C655C626C655F73656D
+:1070B0007276696365735C626C655F6469732E6385
+:1070C00000000000322000200A18000034200020B8
+:1070D000292A00003C200020442000204C200020D1
+:1070E000542000205C200020642000206C20002020
+:1070F000742000208A88428270470022D243428254
+:107100007047F8B504460E46207D002820D0B51DF6
+:107110002988608981421AD1288B022817D12068DA
+:10712000002814D000BF00BFA97EEA7E12021143DE
+:107130000846C107C90F002902D00020009001E0D5
+:1071400001200090694620462268904700BF00BF9A
+:10715000F8BD70B505460C462088102804D01128CB
+:1071600007D050280FD109E021462846FFF7C2FF7B
+:107170000AE021462846FFF7C0FF05E021462846E1
+:10718000FFF7BFFF00E000BF00BF70BDF0B591B0DA
+:1071900004460D46207D002819D00020099000BF2C
+:1071A00008A8007901090901491C08A801710079A2
+:1071B000F0218843103008A9087100BF697B08A836
+:1071C0004171807906218843801C08A988711C219F
+:1071D0000AA803F044FF08A8007A02218843811C12
+:1071E00008A80172007A10218843217D002901D06E
+:1071F000012100E00021090110221140084308A9E3
+:10720000087200200B900D900E90207D002801D078
+:1072100009A800E000200F900020109000BF01217D
+:10722000684681737749818100BF00200290A97B65
+:1072300068460172E97B4172807A06218843811C8D
+:1072400068468172807A08218843014668468172C7
+:10725000807A10218843014668468172807A41080D
+:10726000490068468172297B0191142104A803F02A
+:10727000F6FE03A8049002A8059001216846018348
+:10728000002141830121818301A80890A088A31DCA
+:1072900004AA0AA9A2DF0646002E02D0304611B089
+:1072A000F0BDA86800284DD000BF01216846817359
+:1072B0005549818100BF00200290287C69460872F0
+:1072C00000BF6846407A0109090168464172407A68
+:1072D000F02188436946487200BF6846807A0621DB
+:1072E0008843811C68468172807A08218843014660
+:1072F00068468172807A102188430146684681720F
+:10730000807A4108490068468172A96800F058FDFA
+:107310000746142104A803F0A2FE03A8049002A8C3
+:1073200005906846078300214183018B8183089083
+:10733000E08822460E3204A9A3DF0646002E03D0C1
+:107340003046ACE70020E0810020A8E7F8B504460D
+:107350000D46286820600020C0436082287920758F
+:10736000FF20207400BF01216846817027490180F9
+:1073700000BF221D69460120A0DF0646002E01D075
+:107380003046F8BD29462046FFF700FFF9E733B540
+:1073900085B004460025207C6946097E88422FD0AE
+:1073A000012104916846007E2074E08806AB04AA9F
+:1073B0000021A4DF0546002D02D0284607B030BDCD
+:1073C000608A1349884219D0207D002816D00020F9
+:1073D000009001900290039001210491E188684699
+:1073E0000180012181700021818004A8029006A8FB
+:1073F0000390608A6946A6DF054600E0082500BFC5
+:107400002846DBE7192A0000082900000F180000B1
+:10741000FFFF00008A88C28270470022D243C282E6
+:10742000704738B505460C46208B022817D12868CE
+:10743000002814D000BF00BFA17EE27E12021143DB
+:107440000846C107C90F002902D00020009001E0C2
+:1074500001200090694628462A68904700BF38BD41
+:1074600070B504460D46AE1D31886089814203D156
+:1074700031462046FFF7D5FF70BD70B505460C4676
+:107480002088102804D0112807D050280FD109E0F7
+:1074900021462846FFF7BEFF0AE021462846FFF7AF
+:1074A000BCFF05E021462846FFF7DAFF00E000BFF9
+:1074B00000BF70BDF7B50C461646002701257119AF
+:1074C000A08800F06EF94019C5B2A2792846691C5F
+:1074D000CDB232540098007EC007C00F00280AD0F9
+:1074E0002078002807D0012007437119208900F077
+:1074F00058F94019C5B20098008B022108400028B5
+:1075000016D06078002813D0022007437119E06874
+:107510000870FF2212020240120A4A70FF2212046F
+:107520000240120C8A70020ECA7004225019C5B2B1
+:107530000098008B04210840002804D0A07800287F
+:1075400001D00420074337702846FEBD30B599B0FE
+:1075500005460C460020119000BF10A800790109D3
+:107560000901491C10A801710079F02188431030ED
+:1075700010A9087100BF217910A8417180790621F6
+:107580008843811C10A881711C2112A803F067FD9B
+:1075900010A8007A10218843103010A9087200202A
+:1075A00013901590169011A817900020189000BF06
+:1075B000012108A881737F496846818500BF0020AA
+:1075C0000A90617908A80172A1794172807A062136
+:1075D0008843811C08A88172807A0821884301466B
+:1075E00008A88172807A10218843014608A8817218
+:1075F000807A41084900491C08A8817214210CA80E
+:1076000003F02DFD0BA80C900AA80D9001AA06A965
+:107610002846FFF74FFF0146684601870021418752
+:107620001421818701A81090A888AB1D0CAA12A96B
+:10763000A2DF19B030BD70B590B005460C461C21D4
+:1076400009A803F00CFD08A9087902218843801CD1
+:1076500008A9087100200A900C900D900E900F90D0
+:1076600000BF0121684681735249491C818100BFD6
+:1076700000200290E17968460172217A4172807A95
+:1076800006218843811C68468172807A08218843DC
+:10769000014668468172807A102188430146684617
+:1076A0008172807A4108490068468172142104A8D9
+:1076B00003F0D5FC6689684606713112417103A852
+:1076C000049002A8059002216846018300214183AD
+:1076D0000221818301A80890A8882B460E3304AAB2
+:1076E00009A9A2DF10B070BDF8B504460E46306897
+:1076F00020600020C043E0827089208300BF012108
+:10770000684681702C49018000BF221D6946012016
+:10771000A0DF0546002D01D02846F8BD31462046A1
+:10772000FFF714FF0546002D01D02846F5E7314646
+:107730002046FFF780FF0546002D01D02846ECE7E4
+:107740000020EAE7F0B58BB004460F46E08A1B49FB
+:10775000884222D006AA39462046FFF7ABFE0646ED
+:10776000059600200190029003900490E1886846FD
+:107770008180012181710021018105A8039006A863
+:107780000490E08A01A9A6DF0546002D04D16846D1
+:10779000808AB04200D00C2500E0082528460BB0B6
+:1077A000F0BD02460A70FF2000021040001248702F
+:1077B00002207047532A000014180000FFFF000049
+:1077C000F7B584B004460F4606980678B01C1F280B
+:1077D00003DC6078801C1F2802DD0C2007B0F0BDA0
+:1077E0001F20801B801E85B20295B11C781802A94B
+:1077F0007DDF03900398002801D00398EEE72078FE
+:10780000022808D168460089A84204DC09200190BA
+:10781000684605890BE0082001906078002804D0B4
+:107820006078A84201DC657801E068460589681C3B
+:10783000C2B23046711CCEB23A543046721CD6B237
+:107840000199395406980178A81C0818C1B2069805
+:1078500001700020C2E7F8B505460C462078001DEF
+:107860001F2801DD0C20F8BD684679DF0646002E92
+:1078700001D03046F7E703212278501C2070A9542C
+:1078800019212278501C2070A9542078411968468B
+:10789000008800F08AFA2178401820700020E2E782
+:1078A000F8B505460F4616461C466868002801D103
+:1078B0000720F8BD2078801C298840181F2801DD8A
+:1078C0000C20F6E72878401CC1B22278501C2070AA
+:1078D000B1542178481C207077542A88237898194D
+:1078E000696803F05DFB207829784018207000203B
+:1078F000DFE730B503461078C01C1F2801DD0C20DF
+:1079000030BD02241578681C10704C550A24157877
+:10791000681C10704C551578681C10704B55002071
+:10792000EEE7FFB587B006461D46109C0020069086
+:1079300020780590002747E0B900706841180A8850
+:107940006846028149884181002203A902A865DFB7
+:1079500004900498002802D004980BB0F0BD68464B
+:10796000017B099881422ED10698002801D0002081
+:1079700000E00220019020786946097B41180198B7
+:1079800008181F2801DD0C20E7E70698002809D118
+:107990002078401C20702278511C08982170A8542F
+:1079A000012006902078421903A902A865DF0490FF
+:1079B0000498002801D00498CFE720786946097B15
+:1079C0004018207000BF7F1C3088B842B4DC069895
+:1079D000002806D021780598401C081AC1B20598E5
+:1079E00029540020B9E7FFB581B0074615460A9E25
+:1079F000009602223846049B0299FFF792FF044644
+:107A0000002C02D0204605B0F0BD00961022294679
+:107A10003846049BFFF785FF0446002C01D0204622
+:107A2000F1E70020EFE701460888062808DB088810
+:107A30001922D201904205DD0888E04A904201D027
+:107A4000072070474888062808DB48881922D20199
+:107A5000904205DD4888D94A904201D00720F0E7DE
+:107A60000888D64A904208D04888904205D00888B5
+:107A70004A88904201DD0720E3E70020E1E7F8B5FE
+:107A800006460D4614462078801D1F2801D90C207B
+:107A9000F8BD3046FFF7C7FF0746002F01D0384634
+:107AA000F6E705212278501C2070A9541221227873
+:107AB000501C2070A95422785119308800F075F9B3
+:107AC00021784018207022785119708800F06DF9E3
+:107AD0002178401820700020DAE7F8B505460E46F8
+:107AE00014462879801CC7B22078801CC0191F2832
+:107AF00001DD0C20F8BD781CC1B22278501C20702A
+:107B0000B154FF212278501C2070B1542278911971
+:107B1000288800F04AF9217840182070A8880028A9
+:107B20000EDDA868002801D10720E3E7AA882378A2
+:107B30009819A96803F034FA207829794018207040
+:107B40000020D7E7FEB507460E461446386B0028DE
+:107B500001D10720FEBD0020019032E0396B0C22DC
+:107B6000019850430D182879801CC0B200900098ED
+:107B7000401CC1B22278501C2070B15416212278CA
+:107B8000501C2070B15422789119288800F00DF90A
+:107B9000217840182070A88800280EDDA8680028E9
+:107BA00001D10720D6E7AA8823789819A96803F09D
+:107BB000F7F920782979401820700198401CC0B24C
+:107BC00001903420C15D01988142C7DC0020C1E7EB
+:107BD000F8B504460F461646002500203070207880
+:107BE000002809D0324639462046FFF7E9FD054610
+:107BF000002D01D02846F8BDA078002808D03146D5
+:107C00003846FFF728FE0546002D01D02846F2E74A
+:107C1000A08800280ADD33463A460121201DFFF7DF
+:107C20003FFE0546002D01D02846E4E7E068002825
+:107C30000BD00020E168085632463946FFF759FE5E
+:107C40000546002D01D02846D5E7208A00280CDD06
+:107C50003B4606220221204610300096FFF7C3FE65
+:107C60000546002D01D02846C5E7208B00280CDDF5
+:107C70003B4607220321204618300096FFF7B3FE4B
+:107C80000546002D01D02846B5E7208C00280CDDE4
+:107C90003B4615221421204620300096FFF7A3FE14
+:107CA0000546002D01D02846A5E7A06A002809D086
+:107CB00032463946A06AFFF7E2FE0546002D01D0A4
+:107CC000284698E7E06A002809D032463946E06A3B
+:107CD000FFF703FF0546002D01D028468BE734202F
+:107CE000005D002809DD324639462046FFF72AFFAD
+:107CF0000546002D01D028467DE728467BE7014652
+:107D00008888002808D08868002805D0886800780E
+:107D100004221040002801D1072070470020FCE712
+:107D200001468888002801DD072070470020FCE715
+:107D3000F3B593B00D4600211291119113980028CC
+:107D400014D01398FFF7DBFF0446002C02D0204626
+:107D500015B0F0BD12AA09A91398FFF739FF044620
+:107D6000002C01D02046F3E709AE00E00026002DEC
+:107D700013D02846FFF7D4FF0446002C01D020463C
+:107D8000E6E711AA01A92846FFF722FF0446002CC6
+:107D900001D02046DCE701AF00E0002710A80379FE
+:107DA0003A46017A304672DFD2E702460A70FF2077
+:107DB000000210400012487002207047FFFF0000D0
+:107DC000F8B505460E46002432782146641C6A54F4
+:107DD00072782146641C6A5400BF022C01D1012034
+:107DE00000E000200746002F05D100BF08A2202197
+:107DF000FEF780FA00BF00BF2046F8BD70B504460C
+:107E00000D46284603F0AAF82080656070BD00008A
+:107E10002E2E5C2E2E5C2E2E5C2E2E5C2E2E5C5377
+:107E20006F757263655C626C655C626C655F7365DF
+:107E30007276696365735C626C655F7372765F63AB
+:107E40006F6D6D6F6E2E63000A7B002A04D04A6846
+:107E500002600022027103E00A68026001220271DE
+:107E600070470246107900280FD048681368C01A7E
+:107E70008B68984204D910688B68C018106013E0B2
+:107E800048681060002010710EE010680B68C01A7E
+:107E90008B68984204D910688B68C01A106003E0A0
+:107EA00008681060012010711068704710B5002834
+:107EB00019DAFB4A03071B0F083B9B089B00D258AB
+:107EC0008307DC0EFF23A3409A438B071B0E840716
+:107ED000E40EA3401A43F24B0407240F083CA40805
+:107EE000A4001A5118E0EF4A03231B02D21883089A
+:107EF0009B00D2588307DC0EFF23A3409A438B07D5
+:107F00001B0E8407E40EA3401A43E64B032424020D
+:107F10001B198408A4001A5110BD10B50446E2488C
+:107F2000846003211120FFF7C1FF10BD10B50120AF
+:107F30000004DE494860DD4940394860112000F006
+:107F4000F5FC112000F0E4FC0120D94908602F2045
+:107F5000FEF7FAF80120D749087010BD10B51120BE
+:107F600001218140CF4A8032116000BF012000040E
+:107F7000CE498860CD49403988600120CC494860AD
+:107F80002F20FEF7E1F80120C94988600020CA4986
+:107F900008602F20FEF7D8F80020C649087010BDF1
+:107FA000F0B5024624205043C44B1B68C118C44896
+:107FB0000068401C002802D1C148026059E0C04856
+:107FC0004B68006824246043BC4C24680019406856
+:107FD000834217D8BA48006824235843B74B1B681C
+:107FE000C01840684B68C31AB54800682424604331
+:107FF000B24C246800194360B14800680862B04878
+:10800000026036E04B68AE4D2C6828680DE02425F0
+:108010004543AA4E3668AD196D685B1B044624259E
+:108020004543A64E3668AD19286A451C002D07D079
+:1080300024254543A14E3668AD196D689D42E6D3AF
+:10804000451C002D0CD0242545439C4E3668AD19A7
+:108050006D68EE1A24254543984F3F68ED196E6010
+:108060004B60086224256543944E3668AD192A6238
+:1080700000BFF0BDF8B50646914805682C4609E0FA
+:10808000B44200D109E02546242060438B490968A9
+:108090004018046A601C0028F2D100BF601C002850
+:1080A00000D1F8BDA54210D185480068242148437D
+:1080B000824909684018006A8149086008460068DA
+:1080C000401C002801D1FFF749FF242060437B4971
+:1080D00009684018476824206043784909684018B7
+:1080E000016A24206843754A1268801801622420BE
+:1080F0006843724909684018046A601C00280CD063
+:10810000242060436D49096840184068C119242043
+:1081100060436A4A12688018416000BFC1E710B529
+:10812000112000F0FCFB10BD10B5142000F0F7FB8F
+:1081300010BD70B5044663480068002813D0614A3A
+:10814000E169A06912689047054600BF2E46002EDF
+:1081500007D000BF5CA2FF2164313046FEF7CAF8A9
+:1081600000BF00BF02E0A169E069884770BDF8B5B3
+:1081700053480068401C00283AD0002700F008FC53
+:1081800004464D480168204600F005FC06464C4870
+:10819000056811E02420684348490968441860686C
+:1081A000B04200D90BE06068361A6068C719256ACA
+:1081B0002046FFF7BEFF00BF681C0028EAD100BFC1
+:1081C0004D4800784D490978884209D14B480078DC
+:1081D000401CC0B249490870022801D10020087033
+:1081E00046480078800046490F50FFF79DFF00BFCA
+:1081F000F8BD014640480078404A127890421DD0B0
+:108200003D480078401C3C4A107010460078022817
+:1082100001D100201070384800788000384A10588A
+:108220000860254A086812688018234A10601046C2
+:1082300000680002000A10600120704700200860FA
+:1082400000BFFAE7FEB51E48006801902D4800788F
+:10825000009076E00098C0002B4909684418257802
+:108260006BE01820684361680F18681CC5B2A078DD
+:10827000A84200D100253878022802D0032859D11D
+:1082800044E02421786848430C4909684618307D49
+:10829000002804D07868FFF7EDFE002030754AE032
+:1082A0001CED00E000E100E000150140401301403A
+:1082B00000100140A42000209020002080200020F9
+:1082C0008C200020A02000202E2E5C2E2E5C2E2E36
+:1082D0005C2E2E5C2E2E5C536F757263655C617034
+:1082E000705F636F6D6D6F6E5C6170705F74696DF0
+:1082F00065722E63000000009C2000209D2000205D
+:108300009420002084200020882000200CE0F849E0
+:10831000096824225143F74A126888180021017520
+:10832000F34A016A116000BFF1480068401C002850
+:10833000EDD100E000BF00BF00BF6078A84290D13F
+:1083400000BF0098411EC9B20091002882D1E848C0
+:1083500001680198814201D00120FEBD0020FCE7A8
+:10836000F0B50346002426E0E14E366824277E431C
+:10837000E04F3F68F01946689E4203D94668F61AF6
+:1083800046601DE046689B1B46683419002646601F
+:108390000675D74E3568D64F066A3E600669002ED0
+:1083A00008D00E193602360A86600669C66016685D
+:1083B0000662156000BFCE480068401C0028D3D17B
+:1083C00000BFF0BDFEB50746C94800680290CA4824
+:1083D000007801906BE00198C000C8490968451811
+:1083E0005DE0781C002807D03E4624207043C14938
+:1083F00009684418276A23E02978182359436A68D2
+:1084000050182978491C29702978AA78914201D1FD
+:1084100000212970466824217143B64A12688C18DD
+:108420000178012902D1217D002900D037E081683F
+:10843000A160C168E160016921614169E16100BF3A
+:10844000AF49A0680968401A0002000AAD49884295
+:1084500008D2AB49A068096800F09DFAE1684018AD
+:10846000606011E0A648A168006800F094FA0090EE
+:10847000E1680098814204D9E1680098081A6060B8
+:1084800001E00020606000BF0020A060E0600120EB
+:108490002075801E20623046FFF782FD00BF781CE9
+:1084A00000289ED12878697888429AD100BF019827
+:1084B000411EC9B2019100288DD18D4801680298F2
+:1084C000814201D00120FEBD0020FCE7FEB58848B6
+:1084D0000068401C002834D08548006824214843A7
+:1084E000844909684018456800F052FA01908448B0
+:1084F00004682146019800F04EFAC61C82480078B4
+:10850000002801D1FFF712FDAE4201D2284600E05B
+:10851000304604192402240A20467C49086000BF22
+:1085200000F036FA00900199009800F034FAC71C68
+:108530002046019900F02FFA874201D9FFF7EFFD9D
+:1085400001E0FFF70BFDFEBDFEB50020C04301902A
+:108550006B480468664807686846FFF74AFE0546A8
+:10856000FFF770FE0646002D05D001AA21460098AF
+:10857000FFF7F6FE01260198FFF724FF002800D040
+:108580000126002E02D03846FFF7A0FFFEBD417045
+:10859000704770B502460B465078411C907888426F
+:1085A00000D100211078884201D1002070BD1960EF
+:1085B00050781826704355682C182046F6E7FFB50A
+:1085C00081B005460E461746E9004C4A12688818E5
+:1085D0006946FFF7DEFF0446002C02D1042005B0F7
+:1085E000F0BD01202070666000F0D2F9A060E76065
+:1085F000049820610A986061E900404A126888186E
+:108600000099FFF7C4FFFFF78FFD0020E7E7F8B5FB
+:1086100004460E46E100394A126888186946FFF799
+:10862000B8FF0546002D01D10420F8BD02202870B6
+:108630006E60E100314A126888180099FFF7A7FFC1
+:10864000FFF772FD0020F0E738B50446E1002B4A41
+:10865000126888186946FFF79CFF0546002D01D176
+:10866000042038BD03202870001F6860E100234A01
+:10867000126888180099FFF78AFFFFF755FD002060
+:10868000EFE710B500202249086000212048416032
+:108690008160C16000201E49403908604860FFF7D2
+:1086A00066FD10BD10B5FFF74FFF10BDFFB581B0DF
+:1086B0000E4617461C4620468107890F002901D126
+:1086C000012100E00021002902D1072005B0F0BD02
+:1086D000002C01D10720F9E7FFF740FC0D490A986B
+:1086E00008600D48067003480460002523E0000080
+:1086F0008C20002080200020842000208820002062
+:1087000090200020FFFF7F00A420002040150140A2
+:1087100040110140A02000207C20002000212420C6
+:1087200068439F4A12681154242068439C4A126887
+:10873000801801756D1CB542F0DB242070430419CC
+:1087400003209849087098480460183400250CE00C
+:10875000E900954A126888180021017041708770FD
+:108760004460182179430C196D1C032DF0DB0020A7
+:10877000C0438E49086000208D4908708D490870FB
+:10878000142000F0D3F803211420FFF78FFB1420EE
+:1087900000F0BEF80198FFF7C0FB00F0F9F8864939
+:1087A0000860002092E770B503460C467C480068DC
+:1087B000002801D1082070BD002A01D10720FAE766
+:1087C000002B01D10720F6E700211CE024204843BC
+:1087D000734D2D68285C002814D1012524204843BE
+:1087E0006F4E36683554242048436D4D2D6840192E
+:1087F0004470242048436A4D2D68401982611960F5
+:108800000020D8E7491C6D4800788142DEDB042057
+:10881000D1E710B500F092F8012802D0032804D166
+:1088200001E0002403E0012401E0022400BF00BFB6
+:10883000204610BDFEB504460D4616465848006851
+:10884000002801D10820FEBD5C480078844201D296
+:10885000052D01D20720F6E724206043504909681E
+:10886000085C012801D00820EDE7242060434C4932
+:10887000096840184078012801D1284600E000200E
+:108880000746FFF7C6FF3B462A4621460096019061
+:10889000FFF795FED7E770B5044641480068002809
+:1088A00001D1082070BD45480078844201D30720DB
+:1088B000F8E7242060433A490968085C012801D0A0
+:1088C0000820EFE7FFF7A5FF05462146FFF79FFECB
+:1088D000E8E710B532480068002801D1082010BD33
+:1088E000FFF797FF0446FFF7AFFEF8E710B5044621
+:1088F00000F04EF82060002010BD70B505460E4611
+:1089000014463146284600F046F82060002070BD2D
+:10891000C206D20E01219140294A11607047C20659
+:10892000D20E01219140274A11607047C206D20E33
+:1089300001219140234A80321160704710B52248CE
+:108940004068C105C90D002920D00A46103A50B22E
+:1089500000280DDA1C4B1C330407240F083CA40824
+:10896000A4001B598407E40EE3401B069B0F0BE099
+:10897000134B032424021B198408A4001B598407E9
+:10898000E40EE3401B069B0FD8B210BD0420FCE7A9
+:108990000E48406870470246501A0002000A7047AD
+:1089A0008020002084200020882000208C200020AF
+:1089B0009C2000209D200020902000207C20002072
+:1089C00000E100E000E200E000ED00E00015014001
+:1089D00070B506460C46154629462046FDF798FC1C
+:1089E00070BDF8B55A480078002800D1F8BD594844
+:1089F0000068002801D1012000E00020054656480B
+:108A00000068002801D1012000E00020064600BFD8
+:108A1000002D14D1684651DF0446052C01D10125F3
+:108A20000CE0002C06D000BF4CA269212046FDF7C7
+:108A300061FC03E0474900980968884700BF002EA1
+:108A40001AD154480088009069465348006861DF95
+:108A50000446052C01D101260DE0002C06D000BFF4
+:108A60003EA280212046FDF745FC04E04A4800680C
+:108A700039490968884700BF002D02D0002E00D078
+:108A800000E0C5E700BF00BFB0E7FFB581B00C460E
+:108A900016461F46002C02D1072005B0F0BD204627
+:108AA0008107890F002901D1012100E0002100295F
+:108AB00001D10720F1E7384804603648068037487E
+:108AC00007603749019810DF0546002D01D0284680
+:108AD000E3E701201E490870162026DFDDE710B508
+:108AE00011DF0446002C01D0012000E000201849CD
+:108AF0000870204610BD0146002901D10E207047A4
+:108B0000154801600020FAE70146002901D10E2036
+:108B10007047104801600020FAE770B51F480068F0
+:108B2000002811D01D4800688047044600BF254634
+:108B3000002D07D000BF09A2FF2133312846FDF7E1
+:108B4000D9FB00BF00BF01E0FFF74BFF70BD000085
+:108B5000B2200020B8200020B42000202E2E5C2E51
+:108B60002E5C2E2E5C2E2E5C2E2E5C536F75726347
+:108B7000655C73645F636F6D6D6F6E5C736F66745D
+:108B80006465766963655F68616E646C65722E63A7
+:108B900000000000B0200020AC200020A820002011
+:108BA000D189010001464888B74A1288904206DB05
+:108BB0004888B54A5288904201DC01207047002065
+:108BC000FCE738B50546B1480088B14988423BD03A
+:108BD000B0480078401CAF490870AF48007B097866
+:108BE00088420FDBA849A948008875DF0446002C9D
+:108BF00007D0A9488069002803D0A748816920468A
+:108C0000884721E00020A3490870A348007C002881
+:108C10000FD03B219D48008876DF0446002C07D00A
+:108C20009D488069002803D09B4881692046884779
+:108C300000BF99484069002806D00020009096485F
+:108C400041696846884700BF38BD70B505461C229B
+:108C500029469148FDF788FA002090490870286855
+:108C600000280BD008228848296802F099F9864824
+:108C70007ADF0446002C08D0204670BD82487BDF96
+:108C80000446002C01D02046F7E70020C0437F496E
+:108C9000088000207F490870814A00218148FFF741
+:108CA00082FDEAE710B57F480068FFF7F4FD10BDCC
+:108CB00038B57D48FFF776FF002829D1774800783E
+:108CC00000280AD074484069002820D00020009075
+:108CD000714841696846884719E06E480078002865
+:108CE00002D16D48456801E06B488568002229463D
+:108CF0006C480068FFF79EFD0446002C07D06648CC
+:108D00008069002803D064488169204688470AE0CA
+:108D100061484069002806D0012000905E48416902
+:108D20006846884700BF00205C49087038BD10B510
+:108D30000446A08855490880082221460E315A4829
+:108D400002F02EF90020534908705348C0890028CA
+:108D500001D1FFF7ADFF10BD70B505460020C0433F
+:108D60004A49088000204B4908704E480068FFF7C8
+:108D700092FD0446002C07D047488069002803D0A4
+:108D8000454881692046884770BD70B50546AC1DD1
+:108D900020884149C989884220D1208B02281DD1D1
+:108DA00000BF00BFA17EE27E120211430846C10748
+:108DB000C90F002902D0FFF77BFF0FE03948006898
+:108DC000FFF769FD0646002E07D03348806900286A
+:108DD00003D0314881693046884700BF70BD10B567
+:108DE00004460822A11D304802F0DAF8FFF760FFC0
+:108DF00010BD10B504462088102806D0112808D0D0
+:108E000012280ED0502810D107E02046FFF78FFF20
+:108E10000CE02046FFF7A0FF08E02046FFF7B5FF73
+:108E200004E02046FFF7DBFF00E000BF00BF10BDFD
+:108E300038B5044608222146134802F0B1F812481A
+:108E40007ADF0546002D1CD11748FFF7ABFE00283E
+:108E50000BD10120114908700B490C48008875DFBF
+:108E6000054601200B4908700BE00B4840690028BB
+:108E700006D001200090084841696846884700BF35
+:108E80000025284638BD0000BC200020C620002058
+:108E9000FFFF0000C420002010240020D420002068
+:108EA000C38B0100D0200020C820002070B50346ED
+:108EB0000C460820002115E00C254D43FE4EAD194F
+:108EC000AD7A2540002D0CD0FC4DAB4205D00C25D1
+:108ED0004D43AD192D899D4202D11160002003E060
+:108EE0000520491C0029E7D000BF70BD10B504461D
+:108EF00018216143F24A891808461130062102F010
+:108F0000ACF818206043EE49401800210160416030
+:108F10008160C160FF2118206043E94A8018017414
+:108F2000182060438018C175204601F00EF910BD6D
+:108F30007CB50546A9B26A46E24801F0E7FD044661
+:108F4000002C09D14C21684601F05FFF0446002C3B
+:108F500002D12846FFF7CAFF20467CBDF8B505467A
+:108F60000E46052700240FE018216143D44A8918D2
+:108F7000084610300722294601F0C2FF002802D11E
+:108F80003460002702E0641C072CEDD300BF384694
+:108F9000F8BD10B5044601210C206043C64A801874
+:108FA0008172891E0C20604380180181FF210C20F2
+:108FB00060438018C1720C2161438818072102F0B8
+:108FC0004CF810BD10B50446002010BDF0B587B0B8
+:108FD00007460C461420019061780C225143B64A92
+:108FE0008918088901AA02A9AADF0646002E46D1DF
+:108FF000617818225143B44A8918081D69468A8845
+:1090000002A901F07DFF002812D160780C214843AD
+:10901000A9494018807A0221084002282FD061789F
+:1090200018225143A84A8818182102F018F826E09F
+:10903000607818214843A4490858002801D0A34D5E
+:1090400000E0A34D694689886078182250439E4A03
+:1090500011506178182251439B4A8918081D6946AE
+:109060008A8802A9FDF780F8607818225043964A52
+:109070008118302318223846A847064600BF002032
+:1090800007B0F0BD02460020704770B505460C469B
+:109090006178182251438C4A8818302318222946B7
+:1090A00001F042FE0646002E0DD16078182148439B
+:1090B00085490858401C002805D1002160781822F5
+:1090C0005043814A1150304670BD0246002070471F
+:1090D000F8B50446002600276078182148437A49ED
+:1090E000085800281FD060780C2148437249401866
+:1090F000807A40210840402815D160780C214843EF
+:109100006D494018807A2021084020280BD06078D3
+:10911000182148436C494018061D607818214843BF
+:109120006949085A87B261780C225143624A89180A
+:1091300008893A463146A9DF0546002D00D0654D25
+:109140002846F8BD0146002070470146002902D19B
+:10915000604833387047FF20087048708870C870C6
+:109160000020F7E7FFB58BB00F461D4600BF5A48F9
+:109170000068002801D10FB0F0BD00BF14984C2842
+:1091800000D9F8E70120029007A8FFF7DEFF0646A6
+:1091900000BF0196019E002E06D000BF4F4A5049E5
+:1091A0000198FDF7A7F800BF00BF002168460177CE
+:1091B00008A801700695149805900020019017E00A
+:1091C000019881B203AA3F4801F0A0FC0646002E98
+:1091D0000BD1082203A90B9801F092FE002804D1BC
+:1091E0000198C1B26846817705E00198401C019062
+:1091F00001980728E4D300BF6846807FFF2875D018
+:10920000042F0AD114984C2803D1202008A90870F3
+:1092100082E0402008A908707EE02949681A1821D8
+:1092200001F01FFF0446072C1DD205A809900D98D8
+:10923000002810D16946887F01218140294A12689F
+:109240001140002901D0012100E00021002902D1B4
+:109250000020029060E06946887F00F076FF2020C1
+:1092600008A9087058E02049681A182101F0F9FE91
+:109270000446002C0DD105A80990202108A80170F2
+:1092800068464477807F182148430D49401806906E
+:1092900042E00D49681A182101F0E3FE0446002C53
+:1092A00035D1302108A80170684644770121C17783
+:1092B0000C20604300491AE004250020FFFF000055
+:1092C0002C240020D8200020EC2400205FAC0100DA
+:1092D00095AB0100418000001025002080B2010004
+:1092E000BC040000E0200020D424002036E0401818
+:1092F000807A0221084002280ED06946497F182250
+:109300005143FB4A8818182101F0A9FE04E000200F
+:109310000290402008A90870029801281ED1042F4D
+:1093200006D108A908780321084308A908700EE0AF
+:10933000032F06D108A908780121084308A908705D
+:1093400005E008A908780221084308A9087005A8C3
+:10935000099008A907A80D9A00F03AFF00BF0AE794
+:109360007FB50646002E02D1E24804B070BD00244D
+:109370000BE000BF00210C206043DF4A11500C209D
+:1093800060438018817200BF641C002CF1D000245F
+:1093900003E02046FFF7FDFD641C002CF9D01821E6
+:1093A000D34801F05CFE002406E02046FFF79EFD56
+:1093B000FF20D2490855641C072CF6D34C2168467F
+:1093C00081810721C181CE480290CE4902A801F0D7
+:1093D000E9FA0546002D30D10120CB4908703078DC
+:1093E000002821D100241CE0A1B26A46C54801F042
+:1093F0008DFB0546002D0FD118216143C34A881803
+:1094000000231822694601F08FFC0546002D07D085
+:109410000020BD49087006E00020BB49087002E04A
+:10942000641C072CE0D308E06A469089D289504337
+:1094300081B2B44801F0E9FC0546284695E770B5CD
+:1094400005460C4600BFB0480078002802D1A94864
+:10945000801F70BD00BF002D01D1A648F9E7002C88
+:1094600001D1A448F5E72068002801D1A148F0E720
+:10947000A148006800280FD19F4920680860062293
+:10948000A11D9D48001D01F08BFD20799A4988722D
+:1094900000202870002601E0964E0A3E3046D8E7AC
+:1094A00070B5044600BF98480078002802D1914862
+:1094B000801F70BD00BF002C01D18E48F9E700BFAE
+:1094C0002078012806DA20780C2148438A49085878
+:1094D000002802D18748801FEBE700BF00BF6078FB
+:1094E000012807DA60780C21484389494018807ABE
+:1094F000012802D17F48801CDBE700BF7D4DAD1FF6
+:1095000060780C21484382494018807A0221084043
+:10951000022809D161780C2251437D4A89180889B3
+:109520007549091D7EDF05462846C2E7024600BF91
+:1095300075480078002802D16E48801F704700BF30
+:10954000002A01D16B48F9E7002901D16948F5E704
+:1095500000BF1078012806DA10780C235843664BB8
+:109560001858002802D16348801FE7E700BF00BFFA
+:109570005078012807DA50780C235843644BC01800
+:10958000807A012802D15B48801CD7E700BF507861
+:109590000C2358435E4BC018807A042318400028DF
+:1095A00002D0012008700EE050780C235843584B2D
+:1095B000C018807A40231840002802D0022008708A
+:1095C00001E0002008700020B8E7F0B5044600BFB5
+:1095D0004D480078002802D14648801FF0BD00BFEA
+:1095E000002C01D14348F9E7002901D14148F5E7B2
+:1095F0000868002801D13F48F0E78868002801D1B9
+:109600003C48EBE700BF2078012806DA20780C25DB
+:109610006843394D2858002802D13648801FDDE7BD
+:1096200000BF00230022002024E018254543374EC8
+:10963000AD19ED7DED07ED0F002D08D1182545433F
+:10964000AD1997008E68F5512C4EB054521C182558
+:1096500045432E4EAD19ED7D02263540002D08D133
+:1096600018254543294EAD1910359F000E68F55158
+:109670005B1C401C072805D20D799D4202D90D7B49
+:109680009542D2D80B710A730020A7E70246194809
+:109690008230704770B5044600BF1B480078002830
+:1096A00002D11448801F70BD00BF002C01D11148A9
+:1096B000F9E700BF2078012806DA20780C2148431A
+:1096C0000D490858002802D10A48801FEBE700BF67
+:1096D00000BFA078072807DAA078182148430B4973
+:1096E0004018C07DFF2815D10248801CDBE7000030
+:1096F000EC2400200E80000010250020E520002032
+:1097000065910100D8200020E42000202C240020B6
+:109710000425002000BFA078FFF70AFC0546284674
+:10972000C1E770B5044600BFFE480078002801D1AB
+:10973000FD4870BD00BF002C02D1FB48801DF8E73A
+:1097400000BF2078012806DA20780C214843F74929
+:109750000858002801D1F448EBE700BF0026002597
+:109760000BE018206843F2494018C07DFF2803D061
+:109770002846FFF7DDFB06466D1C072DF1D330466A
+:10978000D7E7FEB504460D4600BFE648007800283E
+:1097900001D1E548FEBD00BF002C02D1E248801D8A
+:1097A000F8E7002D02D1E048801DF3E700BF2078E4
+:1097B000012806DA20780C214843DC4908580028A3
+:1097C00001D1D948E6E700BF00BF6078012807DA79
+:1097D00060780C214843D7494018807A012802D18B
+:1097E000D1480830D6E700BF00BFA078072807DAC5
+:1097F000A07818214843CE494018C07DFF2802D1E7
+:10980000C9480830C6E700BFA868002812D06868B9
+:1098100000280FD0686814280CD22878012809D1B4
+:10982000637818277B43C44FDB19181D6A68A96841
+:1098300001F0B6FBA1786A46C04801F067F9064618
+:1098400028788000BE490A58214668469047064657
+:1098500030469FE77CB504460D4600BFB14800780E
+:10986000002801D1B0487CBD00BF002C02D1AE4819
+:10987000801DF8E7002D02D1AB48801DF3E700BF43
+:109880002078012806DA20780C214843A749085897
+:10989000002801D1A448E6E700BF00BFA078072850
+:1098A00007DAA07818214843A1494018C07DFF2855
+:1098B00002D19D480830D6E700BF60780C214843AC
+:1098C0009C494018807A02210840022802D096481C
+:1098D0008930C8E7A868002812D1686814280FD218
+:1098E000287801280CD16078182148439249401803
+:1098F000001DA8606078182148438F4908586860A7
+:10990000A1786A468D4801F001F906462878800062
+:109910008C490A5821466846904706463046A2E7D9
+:10992000014600BF7F480078002801D17E4870477B
+:1099300000BF002902D17C48801DF8E700BF0878ED
+:10994000012806DA08780C225043784A105800287B
+:1099500001D17548EBE700BF00BF8878072807DA18
+:10996000887818225043724A8018C07DFF2802D19F
+:109970006D480830DBE700BF6B488830D7E7024608
+:109980006948893070470246674889307047014608
+:10999000654889307047024600BF62480078002859
+:1099A00001D16148704700BF002902D15E48801D87
+:1099B000F8E7002A02D15C48801DF3E700BF107869
+:1099C000012806DA10780C235843584B1858002801
+:1099D00001D15548E6E700BF107808700020E1E7A4
+:1099E000FEB504460E46A1786A46544801F08EF84A
+:1099F0000546002D49D160780C2148434D49401857
+:109A0000807A20210840202801D0022E01D14E4F1B
+:109A100004E0012E01D14D4F00E04D4FA0781822F7
+:109A20005043434A8118002318226846B847054628
+:109A3000002D1AD1022E18D060780C2148433D49E0
+:109A40004018807A2021884361780C225143394A9A
+:109A5000891888726078182250433E4A811818236A
+:109A60001A466846B847054600BF022E0DD020783A
+:109A70000C2148432D494018807A800030490A580B
+:109A8000214668469047054600BFFEBD70B50446B6
+:109A90000D4600BF23480078002801D1224870BD40
+:109AA00000BF002C02D12048801DF8E7002D02D114
+:109AB0001D48801DF3E700BF2078012806DA2078D2
+:109AC0000C21484319490858002801D11648E6E7F7
+:109AD00000BF00BFA078072807DAA0781821484304
+:109AE00013494018C07DFF2802D10F480830D6E73F
+:109AF00000BF6078FF2839D12878022836D0A178B5
+:109B0000182251430A4A891808461030072229466C
+:109B100001F046FAA078012181400F4A12681143F2
+:109B20000D4A1BE0E42000200880000010250020E2
+:109B30002C24002004250020EC240020D820002024
+:109B40004CB201005CB201005FAC010095AB0100BA
+:109B5000C58F0100D4240020E0200020116000BF48
+:109B600002212046FFF73CFF002600E0FE4E304673
+:109B700095E770B504460E4600BFFC480078002803
+:109B800002D1F948401C70BD00BF002C02D1F6483C
+:109B9000C01DF8E7002E02D1F348C01DF3E700BF57
+:109BA0002078012806DA20780C214843F04908582B
+:109BB000002802D1EC48401CE5E700BFEA4DAD1E8D
+:109BC000A078FF2817D16078FF282AD060780C2170
+:109BD0004843E8494018807A02210840022820D1F1
+:109BE00060780C225043E34A81180722304601F086
+:109BF000D7F9002515E0A07818214843DE49401820
+:109C0000C07D0221084000280BD1A07818225043C3
+:109C1000D94A8018014610310722304601F0C0F9B8
+:109C200000252846AFE7F0B58BB004460026FF209C
+:109C3000049000BFCD480078002801D10BB0F0BDE2
+:109C400000BF00BFCA480068002800D1F6E700BF87
+:109C500008A8FFF77AFA054600BF2F46002F06D066
+:109C600000BFC64AC6493846FCF744FB00BF00BFE8
+:109C70000020039009900025201D0690342168469D
+:109C80008183FF2108A881700021017000200A90C3
+:109C90002088102813D0A0880AAA0221FFF706F90D
+:109CA0000546002D0BD10C210A984843B1494018B4
+:109CB000C17A08A881700A98C1B208A841702088AA
+:109CC00014287DD006DC10280BD0112879D0132859
+:109CD00078D1DAE0172876D0182875D05228F7D135
+:109CE0000DE20AAF3A460121A648FFF7DFF80290DD
+:109CF0000298002807D102210C22386850439D4A5F
+:109D00008018817201E00420029002980546002D1F
+:109D100071D101261121684601750A98C1B208A8BF
+:109D20004170A1880C220A985043924A8018018100
+:109D300002210C220A9850438E4A801881720C220C
+:109D40000A9951438B4A88180722A11D01F028F96E
+:109D5000607B304000280AD0607B40088A49085C5C
+:109D6000FF2809D0607B4008085C049004E004A947
+:109D7000A01DFFF7F3F80546002D3CD10498C1B2B1
+:109D80000C220A9850437B4A8018C1720C210A9811
+:109D9000484311464018807A082108430C220A994A
+:109DA0005143744A8918887208A9049888700498E5
+:109DB00081B201AA754800F0A9FE0546002D04E015
+:109DC0004EE019E0A4E1A1E078E113D118220A994C
+:109DD00051436F4A881818231A4601A900F0A4FFBE
+:109DE0006348807A80006B490A5808A901A8904707
+:109DF000054600BF00BF8CE10C210A9848435D492D
+:109E00004018807A022188430C220A995143594A0A
+:109E1000891888720C210A98484311464018807AA4
+:109E200008210840082804D1002108A8FFF7D8FD20
+:109E30000AE008A88078FF2806D008A98878FFF7EC
+:109E400055F8FF2008A9887010210C220A98504369
+:109E5000484A80188172012612206946087558E127
+:109E60000C210A98484343494018C07AFF2806D07D
+:109E700018210A98484346494018001D0390A088BD
+:109E80000022039981DF054643E1132069460875E6
+:109E9000A088374A121D00217FDF0546002D02D021
+:109EA0000995002631E00C210A98484331494018B1
+:109EB000807A042108430C220A9951432D4A8918BB
+:109EC000887201260C210A98484311464018C07A2E
+:109ED000FF281AD000BF16206946087518220A9973
+:109EE00051432D4A8918081D142101F0B8F80C219E
+:109EF0000A9848431F494018807A202108430C22C1
+:109F00000A9951431B4A8918887202E10C210A9868
+:109F1000484318494018807A042188430C220A9942
+:109F20005143144A8918887214206946087501261D
+:109F3000A079002802D0A0790990BEE018210A98E3
+:109F4000484313494118E07A887018210A98484319
+:109F50000F494118A07A487018220A9951430C4AB7
+:109F60008918081D142219E007800000E420002051
+:109F700010250020042500202C24002080B20100A0
+:109F8000F3080000FFFF0000E5200020D82000209B
+:109F9000D42400205CB20100EC24002021460C31C6
+:109FA00000F0FEFF0C210A984843A9494018807A26
+:109FB0004021084040286BD10C210A984843A4490D
+:109FC0004018807A082108430C220A995143A04A7C
+:109FD000891888720C210A98484311464018C07AA3
+:109FE000FF2858D10C220A9850430A46811804A829
+:109FF00000F0B3F80546002D4BD108A904988870ED
+:10A000000498C1B20C220A985043914A8018C17238
+:10A01000E07A4007C00F002817D018220A99514350
+:10A020008C4A881810222146203100F0B9FF1821EF
+:10A030000A98484387494018C07D410849001822C2
+:10A040000A985043834A8018C175E07A0007C00F10
+:10A05000002819D018220A9951437E4A89180846C7
+:10A06000103007222146303100F09AFF18210A985B
+:10A07000484378494018C07D0221884318220A9934
+:10A080005143744A8918C875012108A8FFF7A8FC34
+:10A0900013E0099511E00C210A9848436C494018D7
+:10A0A000807A202108430C220A995143684A891872
+:10A0B0008872002108A8FFF793FC2AE00C210A9877
+:10A0C000484363494018807A402108430C220A998A
+:10A0D00051435F4A89188872152069460875002027
+:10A0E000099001265C48807A80005C4A115808A8D3
+:10A0F00088470546002D01D05948099009E0564887
+:10A10000807A8000554A115808A88847099000E0D5
+:10A1100000BF00BF002E16D005A908A8099A00F0BC
+:10A1200057F86846007D12280DD100BF0C210A980F
+:10A13000484347494018807A012802D00A98FEF720
+:10A1400028FF00BF00BF00BF78E501218140454ADC
+:10A1500012688A4343490A607047F8B505460E46BF
+:10A160003F4FBF1C00242EE018206043394940189F
+:10A17000C07DFF2826D13078022814D018206043F3
+:10A180004018C07D0221884318216143314A891853
+:10A19000C8751821614389180846103007223146D6
+:10A1A00000F0FEFE0BE0182060432A494018C07DF5
+:10A1B0004108490018206043264A8018C1752C7058
+:10A1C000002702E0641C072CCED300BF3846F8BD40
+:10A1D000F8B504460D4616461F480768324629461C
+:10A1E0002046B847F8BD02460020704770B50546C6
+:10A1F0000E4631462846FEF7E9FE0446002C02D101
+:10A2000000BF00BF0446204670BD024600207047D4
+:10A2100070B505460E4631462846FEF736FF044621
+:10A22000002C02D100BF00BF0446204670BD01468D
+:10A230000020704770B505462846FEF749FF0446E2
+:10A24000002C02D100BF00BF0446204670BD0000B4
+:10A25000042500202C240020102500206CB20100D1
+:10A2600041800000E0200020002314214143FC4AEB
+:10A2700053541421414389184B8001231421414335
+:10A2800089188B600023142141438918CB60142165
+:10A29000414389180B611421414389188B80704711
+:10A2A00010B50020EF490880EF490870EC49091FFC
+:10A2B000087048708870002403E02046FFF7D4FF40
+:10A2C000641C0A2CF9D310BDFFB583B004460F46B9
+:10A2D00015460326E448007800280BD1002D06D14E
+:10A2E0000698002803D10220DF49087002E001200F
+:10A2F000DD490870DC480078030000F092FF07FD9C
+:10A3000005314E84BAD7FD00D8484069401C00286A
+:10A3100008D001221207126991B2D44A506900F0A4
+:10A32000A0FE02E0012000074069401E012109074C
+:10A33000096989B2484302900120000700690004BE
+:10A34000820C01200007006980B278430146029820
+:10A3500021DF0646002E02D10220C3490870D0E05A
+:10A36000384620DF0646002E16D1002D11D1069862
+:10A3700000280AD12078042803D10720BA490870A0
+:10A380000AE00520B849087006E00420B6490870C4
+:10A3900002E00320B4490870B3E0B4484069401CAF
+:10A3A000002808D001221207126991B2AF4A506901
+:10A3B00000F057FE02E0012000074069401E012125
+:10A3C0000907096989B2484301210907029009690F
+:10A3D00089B2794308462A46029921DF0646002EB3
+:10A3E00010D1069800280AD12078042803D106202D
+:10A3F0009D49087006E005209B49087002E0042092
+:10A40000994908707DE099484069401C002808D0AF
+:10A4100001221207126991B2944A506900F021FE9C
+:10A4200002E0012000074069401E01210907096977
+:10A4300089B24843A90040186188401801210907E2
+:10A440000290096989B27943AA008918628888183C
+:10A45000069A029921DF0646002E09D120780428A9
+:10A4600003D106208049087002E005207E4908706B
+:10A4700047E001200007006980B27843AB00C018B4
+:10A480000290608882082169029821DF0646002E2A
+:10A490000BD1002D06D10698002803D10720724960
+:10A4A000087002E00620704908702AE06F48406991
+:10A4B000401C002808D001221207126991B26B4A91
+:10A4C000506900F0CEFD02E0012000074069401E07
+:10A4D00001210907096989B2484301900120000759
+:10A4E000006981B2019800F0BCFD029020DF0646B1
+:10A4F000002E04D1072000E002E05B49087000E074
+:10A5000000BF00BF304607B0F0BDF0B585B00F27E3
+:10A510005348001F00781421484351494418E56806
+:10A520002078022804D0042823D005286FD16FE0BA
+:10A53000206903904B48008886026088801B049045
+:10A54000039880190390A088801945190121890278
+:10A550000498884206D2049882082846039921DF8D
+:10A56000074605E0FF2201322846039921DF07460E
+:10A5700081E0A06800013E49401800890490A0686D
+:10A580000001401840890390A0680001401840680D
+:10A5900002900298A84205D1039904984843618823
+:10A5A000884202D0A06802280DD10120000700696E
+:10A5B00081B2284600F055FD2A49098846183046E0
+:10A5C00020DF074622E001200007006981B228460B
+:10A5D00000F047FD064601200007006980B2704385
+:10A5E000281A8008019001200007006980B2711CC0
+:10A5F000484361884919401A800800903146204636
+:10A60000009B019AFFF760FE074600BF33E031E090
+:10A6100001200007006981B2284600F022FD0646AD
+:10A62000A088411901200007006980B27043081A10
+:10A6300080080490A0884019618842180120000712
+:10A64000006980B2711C4843801A8008039031462B
+:10A650002046039B049AFFF737FE07460BE00000F5
+:10A6600020250020F4200020F720002000100010FA
+:10A67000E825002000BF00BF002F02D10120FE49C5
+:10A680008870384605B0F0BDFFB581B00646174664
+:10A690000024F94840780A2837D0F7480078F6496E
+:10A6A00049784018C4B20A2C02DB20460A38C4B2EA
+:10A6B00014206043F049091D0E54142060434018D3
+:10A6C0000761142060434118029805C8CA60886079
+:10A6D00014206043E84A121D801804994180142018
+:10A6E000604380180A9981800025101F8078002817
+:10A6F00005D1FFF70AFF0546112D00D10025DE48E0
+:10A700004078401CDC49487000E00425284605B02C
+:10A71000F0BDF8B50446D848007814214843D6491E
+:10A72000091D0E5C081F007814214843D249091DF9
+:10A73000401880680001D1490D58CF480078142195
+:10A740004843CD49091D40184088091F00900978E9
+:10A7500014225143C84A121D89180B69111F097828
+:10A7600014225143C44A121D891808460830224653
+:10A770003146A847F8BD10B50024BF4840780028EE
+:10A7800009DDFFF7C2FE0446002C04D0112C02D0D4
+:10A790002046FFF7BEFF204610BDFEB50546002649
+:10A7A000B548807801287DD10020B3498870B4482D
+:10A7B0000078082811D1022D03D10020B04908707B
+:10A7C00002E00020AF490870FFF7D5FF0646002ED3
+:10A7D00002D03046FFF79DFFFEBD022D02D0032DB3
+:10A7E00079D174E0A448007814214843A249091D96
+:10A7F0004418A5480088401CA349088020780228F6
+:10A8000007D10846008880026188884201DB012068
+:10A8100000E0002007462078052805D198480078F8
+:10A82000072801D1012000E00020029020780428B0
+:10A8300005D193480078072801D1012000E00020CD
+:10A840000190207804280BD18F480088800261880D
+:10A85000884205DB8A480078002801D1012000E009
+:10A86000002000900298002807D10198002804D108
+:10A870000098002801D1002F1FD0002080490870C7
+:10A880003046FFF746FF7C490878FFF7EDFC0020D3
+:10A890007D49088078484078401E774948700846CE
+:10A8A000007800E01AE0401C0870084600780A288A
+:10A8B00003DB084600780A380870FFF75CFF06469D
+:10A8C000002E02D03046FFF724FF05E003E00D2004
+:10A8D000FFF71FFF00E000BF00BF00BF00BF7BE726
+:10A8E00070B5FFF7DDFC00206849088068484069C2
+:10A8F000401C002808D001221207126991B2644A54
+:10A90000506900F0AEFB02E0012000074069801EA4
+:10A9100001210907096989B248435E49086000209E
+:10A920005949088000BF0DE000210201534B9950A6
+:10A930000201D21811810201D21891810201D218AC
+:10A940005181401C0028EFD008204D490870504824
+:10A950004069401C002808D001221207126991B2F8
+:10A960004B4A506900F07DFB02E00120000740697E
+:10A97000401E01210907096989B2484306460120A2
+:10A980000007006981B2304600F06BFB054620DF0E
+:10A990000446002C04D101203749887039490870D9
+:10A9A000204670BDF8B504460D4600BF3548007816
+:10A9B000002801D10820F8BD00BF002C01D10E20D5
+:10A9C000F9E7002D01D10E20F5E72068002801D11C
+:10A9D0000E20F0E7A08801210907096989B28842A1
+:10A9E00002DCA088102801DA0720E4E7E0880028CC
+:10A9F0001ED027484069401C002808D001221207B9
+:10AA0000126991B2224A506900F02BFB02E001204A
+:10AA100000074069401E01210907096989B24843BE
+:10AA2000E188A28851431B4A12688918884201D2E2
+:10AA30000720C0E720798007800F002801D0072079
+:10AA4000B9E712480088012801D10420B3E70F4874
+:10AA5000008828600F48006868600C49098809016F
+:10AA6000064A891868684860084909880901206809
+:10AA70005050A08805490FE01C250020E825002043
+:10AA8000F7200020F6200020F4200020EC200020F9
+:10AA900000100010F02000200988090189180881A1
+:10AAA000E088FE4909880901891848810027A6889D
+:10AAB000E1884E4300BF781C87B20120000700697F
+:10AAC00080B2B04205D201200007006980B2361A78
+:10AAD00000E0002601200007006980B2F049096803
+:10AAE0004018EF49086001200007006980B2B042B9
+:10AAF000E1D9EA4800880001EA4940188781E7481F
+:10AB00000088401CE5490880002054E73CB5034616
+:10AB100000BFE5480078002801D108203CBD00BFF7
+:10AB2000002B01D10E20F9E7002A01D10E20F5E714
+:10AB30001868002805D118680001DA4C2058002850
+:10AB400001D10720EAE75C68186801940090019C35
+:10AB500018680001D34D401900894843201801901E
+:10AB6000009800012C4600194089009C24016419BA
+:10AB700024896043009C2401641964680019019CC5
+:10AB8000A04201D80720C9E7019C009854601060DA
+:10AB90000020C3E7F8B504460E4615461F4600BF21
+:10ABA000C1480078002801D10820F8BD00BF002E60
+:10ABB00001D10E20F9E7002C01D10E20F5E7206825
+:10ABC000002805D120680001B6490858002801D1A5
+:10ABD0000720EAE720680001B2494018408921684F
+:10ABE0000901B04A8918098948432168090189186F
+:10ABF000496840186168884201D80720D5E7002DD0
+:10AC000006D020680001A74940180089A84201DA4F
+:10AC10000720CAE7E81921680901A24A89180989A9
+:10AC2000884201DD0720C0E7304600F05CF90028CB
+:10AC300004D0384600F057F9002801D11020B4E7BD
+:10AC4000606800F050F9002801D11020ADE72B46D4
+:10AC50003246214602200097FFF716FDA5E7F8B51A
+:10AC600004460E4615461F4600BF8F480078002850
+:10AC700001D10820F8BD00BF002E01D10E20F9E758
+:10AC8000002C01D10E20F5E72068002805D12068AE
+:10AC9000000184490858002801D10720EAE720680C
+:10ACA0000001804940184089216809017D4A8918BE
+:10ACB0000989484321680901891849684018616871
+:10ACC000884201D80720D5E7002D06D02068000172
+:10ACD000744940180089A84201DA0720CAE7E81938
+:10ACE000216809016F4A89180989884201DD072016
+:10ACF000C0E7304600F0F7F8002804D0384600F0EE
+:10AD0000F2F8002801D11020B4E7606800F0EBF8F9
+:10AD1000002801D11020ADE72B4632462146052000
+:10AD20000097FFF7B1FCA5E7FFB581B00C461546CB
+:10AD30001E4600BF5C480078002802D1082005B0FC
+:10AD4000F0BD00BF002C01D10E20F8E701980028CB
+:10AD500001D10E20F3E72068002805D1206800010A
+:10AD600050490858002801D10720E8E72068000171
+:10AD70004C4940184089216809014A4A89180989C3
+:10AD80004843216809018918496840186168884268
+:10AD900001D80720D3E7002D06D0206800014149E3
+:10ADA00040180089A84201DA0720C8E7A8192168DD
+:10ADB00009013C4A89180989884201DD0720BEE75C
+:10ADC000019800F090F8002804D0304600F08BF88D
+:10ADD000002801D11020B2E7606800F084F8002854
+:10ADE00001D11020ABE7606881192A46019800F074
+:10ADF000D7F80095206800012A490F580022032146
+:10AE00002046019BB847002099E7F8B504460D4657
+:10AE100000BF25480078002801D10820F8BD00BFF8
+:10AE2000002C01D10E20F9E72068002805D1206808
+:10AE300000011C490858002801D10720EEE72068CE
+:10AE4000000118494018408921680901154A8918EC
+:10AE500009894843216809018918496840186168CF
+:10AE6000884201D80720D9E7606800F03CF8002844
+:10AE700001D11020D2E723681B010A4FDB19626859
+:10AE80005B68D01A226812013B46D218118900F083
+:10AE9000E8F800290AD00720C0E70000EC200020D5
+:10AEA000F0200020E8250020F620002000202B467E
+:10AEB0000246214600900420FFF7E6FB0646304696
+:10AEC000ACE7014600BF0C480078002801D10820FB
+:10AED000704700BF002901D10E20F9E707484078EC
+:10AEE00008600020F4E701468807800F002801D1A0
+:10AEF000012070470020FCE7F62000201C250020E0
+:10AF0000034610B50B439B070FD1042A0DD308C885
+:10AF100010C9121FA342F8D018BA21BA884201D929
+:10AF2000012010BD0020C04310BD002A03D0D3076C
+:10AF300003D0521C07E0002010BD03780C78401CA1
+:10AF4000491C1B1B07D103780C78401C491C1B1B98
+:10AF500001D1921EF1D1184610BD000030B5441C3D
+:10AF600003E00178401C00290DD08107F9D10B4B7B
+:10AF7000DD0104C8D11A91432940FAD0001B0A060A
+:10AF800003D0C01E30BD001B30BD0A0401D0801E9E
+:10AF900030BD0902FCD0401E30BD0000010101019E
+:10AFA000F8B5042A2CD3830712D00B78491C037000
+:10AFB000401C521E83070BD00B78491C0370401CA9
+:10AFC000521E830704D00B78491C0370401C521E8C
+:10AFD0008B079B0F05D0C91ADF002023DE1B08C991
+:10AFE0000AE0FBF7C1F8F8BD1D4608C9FD401C4644
+:10AFF000B4402C4310C0121F042AF5D2F308C91A1A
+:10B00000521EF0D40B78491C0370401C521EEAD427
+:10B010000B78491C0370401C012AE4D409780170A4
+:10B02000F8BD01E004C0091F0429FBD28B0701D53C
+:10B030000280801CC90700D00270704700290BD025
+:10B04000C30702D00270401C491E022904D38307A3
+:10B0500002D50280801C891EE3E70022EEE7002271
+:10B06000DFE7002203098B422CD3030A8B4211D362
+:10B0700000239C464EE003460B433CD40022430889
+:10B080008B4231D303098B421CD3030A8B4201D379
+:10B0900094463FE0C3098B4201D3CB01C01A524111
+:10B0A00083098B4201D38B01C01A524143098B4261
+:10B0B00001D34B01C01A524103098B4201D30B014A
+:10B0C000C01A5241C3088B4201D3CB00C01A52416F
+:10B0D00083088B4201D38B00C01A524143088B4234
+:10B0E00001D34B00C01A5241411A00D201465241CD
+:10B0F000104670475DE0CA0F00D04942031000D3EC
+:10B100004042534000229C4603098B422DD3030A40
+:10B110008B4212D3FC22890112BA030A8B420CD350
+:10B12000890192118B4208D3890192118B4204D379
+:10B1300089013AD0921100E08909C3098B4201D3F9
+:10B14000CB01C01A524183098B4201D38B01C01A33
+:10B15000524143098B4201D34B01C01A52410309AA
+:10B160008B4201D30B01C01A5241C3088B4201D359
+:10B17000CB00C01A524183088B4201D38B00C01A06
+:10B180005241D9D243088B4201D34B00C01A5241DD
+:10B19000411A00D20146634652415B10104601D36A
+:10B1A0004042002B00D54942704763465B1000D3F4
+:10B1B000404201B50020C046C04602BD70477047FE
+:10B1C0007047754600F022F8AE46050069465346C2
+:10B1D000C008C000854618B020B5FAF7F5FF60BC7E
+:10B1E00000274908B6460026C0C5C0C5C0C5C0C5B1
+:10B1F000C0C5C0C5C0C5C0C5403D49008D467047EB
+:10B200000446C046C0462046FAF798FF00487047FB
+:10B21000F825002001491820ABBEFEE726000200F9
+:10B22000704730B47446641E2578641CAB4204D366
+:10B23000635D5B00E31830BC18471D46F8E700006B
+:10B24000141801000F1801000A180100E7A10100FD
+:10B25000CD8F010085900100EDA101000BA201003E
+:10B260008B900100CB90010011A201002FA20100E0
+:10B27000D19001004591010035A20100FFFFFFFFC1
+:10B280002E2E5C2E2E5C2E2E5C2E2E5C2E2E5C53D3
+:10B290006F757263655C626C655C64657669636535
+:10B2A0005F6D616E616765725C6465766963655F39
+:10B2B0006D616E616765725F7065726970686572F5
+:10B2C000616C2E6300000000D8B201000020002055
+:10B2D000F8000000046101000024F400FFFF0000FA
+:10B2E000000000000000000000000000000000005E
+:10B2F000000000000000000000000000000000004E
+:10B30000000000000000000000000000000000003D
+:10B31000000000000000000000000000000000002D
+:10B32000000000000000000000000000000000001D
+:10B33000000000000000000000000000000000000D
+:10B3400000000000000000000000000000000000FD
+:10B3500000000000000000000000000000000000ED
+:10B3600000000000000000000000000000000000DD
+:10B3700000000000000000000000000000000000CD
+:10B3800000000000000000000000000000000000BD
+:10B3900000000000000000000000000000000000AD
+:10B3A000000000000000000000000000000000009D
+:10B3B000000000000000000000000000000000008D
+:10B3C000000000000000000000000000000000007D
+:04000005000160C1D5
+:00000001FF
diff --git a/app/src/main/res/raw/blinky_arm_s110_v7_0_0.hex b/app/src/main/res/raw/blinky_arm_s110_v7_0_0.hex
new file mode 100644
index 000000000..334b00700
--- /dev/null
+++ b/app/src/main/res/raw/blinky_arm_s110_v7_0_0.hex
@@ -0,0 +1,61 @@
+:020000040001F9
+:1060000068300020E96101000362010005620100BF
+:106010000000000000000000000000000000000080
+:106020000000000000000000000000000762010006
+:106030000000000000000000096201000B62010086
+:106040000D6201000D6201000D6201000D62010090
+:106050000D620100000000000D6201000D620100F0
+:106060000D6201000D6201000D6201000D62010070
+:106070000D6201000D6201000D6201000D62010060
+:106080000D6201000D6201000D6201000D62010050
+:106090000D6201000D6201000D6201000D62010040
+:1060A0000D6201000D620100000000000000000010
+:1060B00000000000000000000000000000000000E0
+:1060C00000F002F800F031F80CA030C808382418AD
+:1060D0002D18A246671EAB4654465D46AC4201D120
+:1060E00000F023F87E460F3E0FCCB6460126334221
+:1060F00000D0FB1AA246AB4633431847900200007B
+:10610000A0020000103A02D378C878C1FAD852072A
+:1061100001D330C830C101D504680C6070471FB589
+:10612000C046C0461FBD10B510BD00F0FEF81146B8
+:10613000FFF7F5FF00F018F800F016F903B4FFF7C9
+:10614000F2FF03BC00F01AF9401E00BF00BF00BF01
+:1061500000BF00BF00BF00BF00BF00BF00BF00BF47
+:1061600000BFF1D170470000082000F019F80920A5
+:1061700000F016F813E0082000F020F8092000F0E5
+:1061800018F8FF20F53000F021F8092000F016F88B
+:10619000082000F00EF8FF20F53000F017F8EAE7CD
+:1061A00003210522120707231B02D2188300D150B6
+:1061B000704701218140044A91607047012181406C
+:1061C000014AD160704700000005005001B505E0AC
+:1061D0000098401E00900348FFF7B6FF0098002883
+:1061E000F6D108BDE703000003210C4802680A430A
+:1061F00002600B4802680A4302600A4880470A4866
+:106200000047FEE7FEE7FEE7FEE7FEE7FEE70000E9
+:1062100006480749074A084B70470000240500401C
+:1062200054050040E1620100C160010068200020C7
+:106230006830002068280020682800203248334950
+:10624000086070473248008CC0B2012812D1304833
+:10625000808C0007000F00280CD12D48806AF021A7
+:106260000840402806D12A48C06A0840002801D1C9
+:10627000012070470020FCE72548008CC0B20128AF
+:106280002CD12348808C0007000F002826D12048FD
+:10629000806AF0210840002806D11D48C06A0840E5
+:1062A000002801D1012070471948806AF021084078
+:1062B000102806D11648C06A0840002801D10120E4
+:1062C000F1E71348806AF0210840302806D11048D1
+:1062D000C06A0840002801D10120E4E70020E2E77D
+:1062E00010B5FFF7C9FF002805D00A480A494860E1
+:1062F000C8130A498861FFF7A5FF002802D00120D2
+:106300000749886010BD00000024F4000020002030
+:10631000C00F00F0DFFF07C000050040006C004028
+:1063200000060040704770477047754600F022F83D
+:10633000AE46050069465346C008C000854618B001
+:1063400020B5FFF765FF60BC00274908B646002668
+:10635000C0C5C0C5C0C5C0C5C0C5C0C5C0C5C0C515
+:10636000403D49008D4670470446C046C046204621
+:10637000FFF7E4FE00487047042000200149182080
+:10638000ABBEFEE726000200704700009C630100E0
+:106390000020002004000000046101000024F4003B
+:04000005000160C1D5
+:00000001FF
diff --git a/app/src/main/res/raw/blinky_s110_v7_1_0.hex b/app/src/main/res/raw/blinky_s110_v7_1_0.hex
new file mode 100644
index 000000000..219391e96
--- /dev/null
+++ b/app/src/main/res/raw/blinky_s110_v7_1_0.hex
@@ -0,0 +1,46 @@
+:020000040001F9
+:1060000008280020F56001000F6101001161010006
+:106010000000000000000000000000000000000080
+:1060200000000000000000000000000013610100FB
+:106030000000000000000000156101001761010070
+:106040001961010019610100196101001961010064
+:1060500019610100000000001961010019610100CF
+:106060001961010019610100196101001961010044
+:106070001961010019610100196101001961010034
+:106080001961010019610100196101001961010024
+:106090001961010019610100196101001961010014
+:1060A00019610100196101000000000000000000FA
+:1060B00000000000000000000000000000000000E0
+:1060C0000348854600F032F8004800470D620100A1
+:1060D00008280020401E00BF00BF00BF00BF00BF57
+:1060E00000BF00BF00BF00BF00BF00BF00BFF1D1B5
+:1060F000704700000321094802680A43026008480B
+:1061000002680A4302600748804707480047FEE7E5
+:10611000FEE7FEE7FEE7FEE7FEE70000240500409D
+:106120005405004051610100C1600100064C012589
+:10613000064E05E0E36807CC2B430C3C984710342F
+:10614000B442F7D3FFF7C0FF80620100A0620100F4
+:1061500010B500F037F8002805D00E490C4848600B
+:10616000C8130D4988610D48018CC9B201290ED1AF
+:10617000818C09070BD1018D0906090F042906D16D
+:10618000808D0006000F02D105490120886010BDF6
+:10619000DFFF07C000050040006C0040C00F00F0AA
+:1061A0000006004002E008C8121F08C1002AFAD108
+:1061B00070477047002001E001C1121F002AFBD187
+:1061C000704700001048018CC9B2012917D1818C99
+:1061D000090714D1018D09060A0F03D1828D120619
+:1061E000120F0ED0090F012903D1828D1206120F52
+:1061F00007D0032903D1808D0006000F01D00020B5
+:106200007047012070470000C00F00F000200321FC
+:10621000104A01252B468340DB011B0F02D083006F
+:106220009B181960401C2028F4D30B4E0B4F002400
+:106230007268395D2846884001469043B06011403D
+:10624000F160FF20F53000F00BF8641C042CEFDB4C
+:10625000EDE7000000070050000500507C620100DF
+:1062600031B5054C04E0401E00902046FFF732FF98
+:1062700000980028F7D138BDE7030000151617185D
+:10628000A06201000020002004000000A4610100C1
+:10629000A46201000420002004080000B461010091
+:0462A0000024F400E2
+:04000005000160C1D5
+:00000001FF
diff --git a/app/src/main/res/raw/blinky_s110_v7_1_0_ext_init.dat b/app/src/main/res/raw/blinky_s110_v7_1_0_ext_init.dat
new file mode 100644
index 000000000..184e499e8
Binary files /dev/null and b/app/src/main/res/raw/blinky_s110_v7_1_0_ext_init.dat differ
diff --git a/app/src/main/res/raw/dfu_mac_3_0.sh b/app/src/main/res/raw/dfu_mac_3_0.sh
new file mode 100644
index 000000000..98883ff79
--- /dev/null
+++ b/app/src/main/res/raw/dfu_mac_3_0.sh
@@ -0,0 +1,203 @@
+#!/bin/sh
+
+# 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.
+#
+# Description:
+# ------------
+# The script allows to perform DFU operation from the computer using Android device as a dongle.
+# The script may be run on Linux or Mac.
+#
+# Requirements:
+# -------------
+# 1. Android device with Android version 4.3+ connected by USB cable with the PC
+# 2. The path to Android platform-tools directory must be added to %PATH% environment variable
+# 3. nRF Toolbox (1.11.0+) or nRF Master Control Panel (2.1.0+) application installed on the Android device
+# 4. "Developer options" and "USB debugging" must be enabled on the Android device
+#
+# Usage:
+# ------
+# 1. Open command line
+# 2. Type "sh dfu.sh -?" and press ENTER
+#
+# Android Debug Bridge (adb):
+# ---------------------------
+# You must have Android platform tools installed on the computer or the path to the adb application set in the PATH.
+# Go to http://developer.android.com/sdk/index.html for more information how to install it on the computer.
+# You do not need the whole ADT Bundle (with Eclipse or Android Studio). Only SDK Tools with Platform Tools are required.
+
+# ==================================================================================
+PROGRAM=$0
+DEVICE=""
+S_DEVICE=""
+ADDRESS=""
+HEX_FILE=""
+HEX_PATH=""
+NAME="DfuTarg"
+INIT_FILE=""
+INIT_PATH=""
+E_INIT_FILE=""
+TYPE=0
+
+# ==================================================================================
+# Common methods
+
+intro() {
+ echo "===================================="
+ echo "Nordic Semiconductor DFU bash script"
+ echo "===================================="
+}
+
+error() {
+ exit 1
+}
+
+quit() {
+ exit 0
+}
+
+usage() {
+ echo "Usage: $PROGRAM [options] hex-file"
+ echo "Options:"
+ echo " -d device serial number. Use \"adb devices\" to get list of serial numbers"
+ echo " -a target device address in XX:XX:XX:XX:XX:XX format"
+ echo " -n name optional device name"
+ echo " -t firmware_type 1=Soft Device, 2=Bootloader, 4=Application, 0 (default)=Auto or all from ZIP"
+ echo " -i init the init packet *.dat file"
+}
+
+# ==================================================================================
+# Write intro
+intro
+
+# ==================================================================================
+# Check ADB
+adb devices > /dev/null 2>&1
+if [ ! "$?" = "0" ] ; then
+ echo "Error: adb is not recognized as an external command."
+ echo " Add [Android Bundle path]\\\android-sdk-macosx\\\platform-tools" to \$PATH
+ error
+fi
+
+# ==================================================================================
+# Parse options
+while getopts ":d:a:n:t:i:" VALUE "$@"
+do
+ case $VALUE in
+ d )
+ DEVICE="$OPTARG"
+ S_DEVICE="-s $OPTARG"
+ ;;
+ a ) ADDRESS="$OPTARG" ;;
+ n ) NAME="$OPTARG" ;;
+ t ) TYPE=$OPTARG ;;
+ i )
+ INIT_PATH="$OPTARG"
+ INIT_FILE=$(basename $INIT_PATH)
+ E_INIT_FILE="-e no.nordicsemi.android.dfu.extra.EXTRA_INIT_FILE_PATH \"/sdcard/Nordic Semiconductor/Upload/$INIT_FILE\""
+ ;;
+ : ) echo "Error: Option -$OPTARG must have a string value.\n"
+ usage
+ error
+ ;;
+ ? ) usage
+ quit
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+# Get the HEX file name or write an error
+if [ "$1" = "" ] ; then
+ echo "Error: HEX file name not specified.\n"
+ usage
+ error
+else
+ HEX_PATH=$1
+ HEX_FILE=$(basename $HEX_PATH)
+fi
+
+# ==================================================================================
+if [ "$DEVICE" = "" ] ; then
+ # Check if there is only one device connected
+ C=$(adb devices | grep -e "device" -e "unauthorized" -e "emulator" | grep -c -v "devices")
+ if [ "$C" = "0" ] ; then
+ echo "Error: No device connected.";
+ error
+ fi
+ if [ ! "$C" = "1" ] ; then
+ echo "Error: More than one device connected."
+ echo " Specify the device serial number using -d option:"
+ adb devices
+ usage
+ error
+ fi
+else
+ # Check if specified device is connected
+ C=$(adb devices | grep -c "$DEVICE")
+ if [ "$C" = "0" ] ; then
+ echo "Error: Device with serial number \"$DEVICE\" is not connected."
+ adb devices
+ error
+ fi
+fi
+
+# ==================================================================================
+# Copy selected file onto the device
+printf "Copying \"$HEX_FILE\" to /sdcard/Nordic Semiconductor/Upload/..."
+adb push $S_DEVICE $HEX_PATH "/sdcard/Nordic Semiconductor/Upload/$HEX_FILE" > /dev/null 2>&1
+if [ "#?" = "1" ] ; then
+ echo "FAIL"
+ echo "Error: Device not found."
+ error
+else
+ echo "OK"
+fi
+
+if [ ! "$INIT_FILE" = "" ] ; then
+ printf "Copying \"$INIT_FILE\" to /sdcard/Nordic Semiconductor/Upload/..."
+ adb push $S_DEVICE $INIT_PATH "/sdcard/Nordic Semiconductor/Upload/$INIT_FILE" > /dev/null 2>&1
+ if [ "#?" = "1" ] ; then
+ echo "FAIL"
+ echo "Error: Device not found."
+ error
+ else
+ echo "OK"
+ fi
+fi
+
+if [ "$ADDRESS" = "" ] ; then
+ # Start DFU Initiator activity if no target device specified
+ printf "Starting DFU Initiator activity..."
+ adb $S_DEVICE shell am start -a no.nordicsemi.android.action.DFU_UPLOAD --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE $TYPE -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/$HEX_FILE" $E_INIT_FILE | grep "Error" > /dev/null 2>&1
+else
+ # Start the DFU service directly
+ printf "Starting DFU service..."
+ adb $S_DEVICE shell am startservice -n no.nordicsemi.android.nrftoolbox/.dfu.DfuService -a no.nordicsemi.android.action.DFU_UPLOAD --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE $TYPE -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_ADDRESS $ADDRESS -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_NAME $NAME -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/$HEX_FILE" $E_INIT_FILE | grep "Error" > /dev/null 2>&1
+fi
+
+if [ "$?" = "0" ] ; then
+ echo "FAIL"
+ echo "Error: Required application not installed."
+ error
+else
+ echo "OK"
+ echo "Select DFU target on your Android device to start upload."
+fi
\ No newline at end of file
diff --git a/app/src/main/res/raw/dfu_win_3_0.bat b/app/src/main/res/raw/dfu_win_3_0.bat
new file mode 100644
index 000000000..2cce233c1
--- /dev/null
+++ b/app/src/main/res/raw/dfu_win_3_0.bat
@@ -0,0 +1,249 @@
+@echo off
+rem Copyright (c) 2015, Nordic Semiconductor
+rem All rights reserved.
+rem
+rem Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+rem
+rem 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+rem
+rem 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+rem documentation and/or other materials provided with the distribution.
+rem
+rem 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
+rem software without specific prior written permission.
+rem
+rem THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+rem LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+rem HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+rem LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+rem ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+rem USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+rem
+rem Description:
+rem ------------
+rem The script allows to perform DFU operation from the computer using Android device as a dongle.
+rem The script may be run on Windows.
+rem
+rem Requirements:
+rem -------------
+rem 1. Android device with Android version 4.3+ connected by USB cable with the PC
+rem 2. The path to Android platform-tools directory must be added to %PATH% environment variable, f.e: C:\Program Files\Android ADT Bundle\sdk\platform-tools
+rem 3. nRF Toolbox (1.6.0+) or nRF Master Control Panel (1.8.0+) application installed on the Android device
+rem 4. "Developer options" and "USB debugging" must be enabled on the Android device
+rem
+rem Usage:
+rem ------
+rem 1. Open command line
+rem 2. Type "dfu -?" and press ENTER
+rem
+rem Android Debug Bridge (adb):
+rem ---------------------------
+rem You must have Android platform tools installed on the computer or the files: adb.exe, AdbWinApi.dll copied to the same directory.
+rem Go to http://developer.android.com/sdk/index.html for more information how to install it on the computer.
+rem You do not need the whole ADT Bundle (with Eclipse or Android Studio). Only SDK Tools with Platform Tools are required.
+rem
+rem ==================================================================================
+setlocal
+set PROGRAM=%~0
+set DEVICE=
+set S_DEVICE=
+set ADDRESS=
+set NAME="DfuTarg"
+set INIT_FILE=
+set INIT_PATH=
+set E_INIT_FILE=
+set TYPE=0
+
+rem ==================================================================================
+rem Check ADB
+
+call adb devices > nul 2>&1
+if errorLevel 1 (
+ call :info
+ echo Error: adb is not recognized as an external command.
+ echo Add [Android Bundle path]/sdk/platform-tools to %%PATH%%
+ goto error
+)
+
+rem ==================================================================================
+rem Check help
+if "%~1"=="" goto usage
+if "%~1"=="-?" goto usage
+if "%~1"=="/?" goto usage
+
+rem ==================================================================================
+rem Read the target device id
+if /I "%~1"=="-d" set TARGET_DEVICE_SET=1
+if defined TARGET_DEVICE_SET (
+ if not "%~1"=="" (
+ set DEVICE=%~2
+ set S_DEVICE=-s %~2
+ shift
+ shift
+ ) else goto usage
+)
+
+rem Read the optional device BLE address. If the address has not been provided user may select the device on the phone/tablet
+if /I "%~1"=="-a" set ADDRESS_SET=1
+if defined ADDRESS_SET (
+ if not "%~1"=="" (
+ set ADDRESS=%~2
+ shift
+ shift
+ ) else goto usage
+)
+
+rem Read the optional device name. The default name is DfuTarg
+if /I "%~1"=="-n" set NAME_SET=1
+if defined NAME_SET (
+ if not "%~1"=="" (
+ set NAME="%~2"
+ shift
+ shift
+ ) else goto usage
+)
+
+rem Read the optional firmware type.
+if /I "%~1"=="-t" set TYPE_SET=1
+if defined TYPE_SET (
+ if not "%~1"=="" (
+ set TYPE=%~2
+ shift
+ shift
+ ) else goto usage
+)
+
+rem Read the optional init file name.
+if /I "%~1"=="-i" set INIT_SET=1
+if defined INIT_SET (
+ if not "%~1"=="" (
+ set INIT_FILE=%~nx2
+ set INIT_PATH="%~f2"
+ set E_INIT_FILE=-e no.nordicsemi.android.dfu.extra.EXTRA_INIT_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/%~nx2"
+ shift
+ shift
+ ) else goto usage
+)
+
+rem If there are some other modifiers, show usage
+set OPTION="%~1"
+if not defined TARGET_DEVICE_SET (
+ rem If there is another option -x which is not supported, show usage
+ if %OPTION:-=% neq %OPTION% goto usage
+)
+
+rem ==================================================================================
+rem Write intro
+call :info
+
+rem Read file name and fully qualified path name to the HEX file
+if "%~1"=="" (
+ echo Error: HEX file name not specified.
+ goto error
+)
+set HEX_FILE=%~nx1
+set HEX_PATH="%~f1"
+if not exist %HEX_PATH% (
+ echo Error: The given file has not been found.
+ goto error
+)
+
+rem ==================================================================================
+if "%DEVICE%"=="" (
+ rem Check if there is only one device connected
+ for /f "delims=" %%a in ('call adb devices ^| findstr "device unauthorized emulator" ^| find /c /v "devices"') do (
+ if "%%a"=="0" (
+ echo Error: No device connected.
+ goto error
+ )
+ if not "%%a"=="1" (
+ echo Error: More than one device connected.
+ echo Specify the device serial number using -d option:
+ call adb devices
+ goto usage_only
+ goto error
+ )
+ )
+) else (
+ rem Check if specified device is connected
+ for /f "delims=" %%a in ('call adb devices ^| find /c "%DEVICE%"') do (
+ if "%%a"=="0" (
+ echo Error: Device with serial number "%DEVICE%" is not connected.
+ call adb devices
+ goto error
+ )
+ )
+)
+
+rem ==================================================================================
+rem Copy selected file onto the device
+echo|set /p=Copying "%HEX_FILE%" to /sdcard/Nordic Semiconductor/Upload...
+call adb %S_DEVICE% push %HEX_PATH% "/sdcard/Nordic Semiconductor/Upload/%HEX_FILE%" > nul 2>&1
+if errorLevel 1 (
+ echo FAIL
+ echo Error: Device not found.
+ goto error
+) else echo OK
+
+if not "%INIT_FILE%"=="" (
+ echo Copying "%INIT_FILE%" to /sdcard/Nordic Semiconductor/Upload...OK
+ call adb %S_DEVICE% push %INIT_PATH% "/sdcard/Nordic Semiconductor/Upload/%INIT_FILE%" > nul 2>&1
+)
+
+if "%ADDRESS%"=="" (
+ rem Start DFU Initiator activity if no target device specified
+ echo|set /p=Starting DFU Initiator activity...
+ call adb %S_DEVICE% shell am start -a no.nordicsemi.android.action.DFU_UPLOAD^
+ --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE %TYPE%^
+ -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/%HEX_FILE%" %E_INIT_FILE% | find "Error" > nul 2>&1
+) else (
+ rem Start DFU service on the device
+ echo|set /p=Starting DFU service...
+ call adb %S_DEVICE% shell am startservice -n no.nordicsemi.android.nrftoolbox/.dfu.DfuService -a no.nordicsemi.android.action.DFU_UPLOAD^
+ -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_ADDRESS %ADDRESS%^
+ -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_NAME %NAME%^
+ --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE %TYPE%^
+ -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/%HEX_FILE%" %E_INIT_FILE% | find "Error" > nul 2>&1
+ )
+
+if %errorLevel%==0 (
+ echo FAIL
+ echo Error: Required application not installed.
+ goto error
+) else (
+ echo OK
+ echo Select DFU target on your Android device to start upload.
+)
+
+goto exit
+
+rem ==================================================================================
+:info
+echo =====================================
+echo Nordic Semiconductor DFU batch script
+echo =====================================
+goto eof
+
+rem ==================================================================================
+:usage
+call :info
+:usage_only
+echo Usage:
+echo %PROGRAM% [-D device_id] [-A address] [-N name] [-t firmware_type] [-I init_file.dat] application.hex
+echo Info:
+echo device_id - Call: "adb devices" to get list of serial numbers
+echo address - The target DFU-supported device address in XX:XX:XX:XX:XX:XX format
+echo name - The optional device name (will be shown on the phone)
+echo firmware_type - 1=Soft Device, 2=Bootloader, 4=Application, 0=Auto (Application or all from ZIP)
+echo init_file - The binary file with the Init Packet
+goto exit
+
+rem ==================================================================================
+:error
+set errorLevel=1
+
+rem ==================================================================================
+:exit
+endlocal
+
+:eof
\ No newline at end of file
diff --git a/app/src/main/res/raw/readme.txt b/app/src/main/res/raw/readme.txt
new file mode 100644
index 000000000..88a6de5bb
--- /dev/null
+++ b/app/src/main/res/raw/readme.txt
@@ -0,0 +1,58 @@
+-----------------------
+ R E A D M E
+-----------------------
+
+The README file contains information about the following files:
+(1) dfu_3_0.bat
+(2) dfu_3_0.sh
+
+------------------------
+ Over-the-air DFU
+------------------------
+Over-the-air DFU represents a highly desirable feature requirement for today's wireless products.
+DFU allows for application bugs to be fixed in the field via updates in a completely transparent
+way for the consumer. This brings a large degree of security to product makers safe in the knowledge
+any software fixes or indeed new feature introductions can be completed automatically via cellphone
+updates that do not require any user interaction.
+
+Nordic's unique software architecture that cleanly separates Application code from protocol stack
+plays an important role in how the OTA DFU operates. The protocol stack together with a boot loader
+can handle the transportation and verification of a new application firmware update. In competing
+systems where application code and protocol stack are compiled as a single entity, the only way to
+do this is to reserve enough spare memory to cover both the protocol stack and the application.
+This can represent an unacceptable memory resource requirement as the SoC needs to facilitate this
+'scratch pad' memory space which is redundant when not in use during an update.
+
+Read more about DFU on http://www.nordicsemi.com
+DFU documentation: http://developer.nordicsemi.com/nRF51_SDK/doc/7.1.0/s110/html/a00062.html
+
+-------------------------
+ Files
+-------------------------
+1. DFU_3_0.BAT
+
+The dfu_*.bat script allows a developer to upload HEX, BIN or ZIP files directly from the PC to a Dfu Target
+without use of a BLE dongle connected to the computer. It transfers file onto the Android
+device and starts service that sends the application, soft device or a bootloader Over-the-Air onto the peripheral.
+The peripheral must be programmed with DFU bootloader. Since the DFU_3_0 the buttonless update is also supported.
+
+Execute dfu -? in the command line for usage.
+
+Android 4.3+ device with nRF Toolbox (1.11.0+) or nRF Master Control Panel (2.1.0+) is required.
+The script runs on Windows OS.
+
+2. DFU_3_0.SH
+
+Similar script for Linux, Mac or Cygwin. Uses sh shell.
+
+-------------------------
+ Change log
+-------------------------
+2.0 - This version contains minor changes to match the modified action names.
+3.0 - Option to send the init packet and firmware type
+
+-------------------------
+ Development kit
+-------------------------
+Go to http://www.nordicsemi.com/eng/Products/Bluetooth-R-low-energy/nRF51822-Development-Kit for more
+information about the Development Kit.
diff --git a/app/src/main/res/values-land-v21/dimens.xml b/app/src/main/res/values-land-v21/dimens.xml
new file mode 100644
index 000000000..5b9851b3f
--- /dev/null
+++ b/app/src/main/res/values-land-v21/dimens.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ 6dp
+ 42dp
+
+
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
new file mode 100644
index 000000000..2de3b4d7e
--- /dev/null
+++ b/app/src/main/res/values-land/dimens.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ 26dp
+ 40dp
+ 238dp
+ 178dp
+
+
+ 6dp
+ 0dp
+
diff --git a/app/src/main/res/values-land/strings_bpm.xml b/app/src/main/res/values-land/strings_bpm.xml
new file mode 100644
index 000000000..a4b8d6499
--- /dev/null
+++ b/app/src/main/res/values-land/strings_bpm.xml
@@ -0,0 +1,26 @@
+
+
+
+ BLOOD PRESSURE
+ -115dp
+
diff --git a/app/src/main/res/values-land/strings_csc.xml b/app/src/main/res/values-land/strings_csc.xml
new file mode 100644
index 000000000..da885e43e
--- /dev/null
+++ b/app/src/main/res/values-land/strings_csc.xml
@@ -0,0 +1,26 @@
+
+
+
+ CSC
+ -13dp
+
diff --git a/app/src/main/res/values-land/strings_dfu.xml b/app/src/main/res/values-land/strings_dfu.xml
new file mode 100644
index 000000000..1a9618d9f
--- /dev/null
+++ b/app/src/main/res/values-land/strings_dfu.xml
@@ -0,0 +1,26 @@
+
+
+
+ DFU
+ -16dp
+
diff --git a/app/src/main/res/values-land/strings_gls.xml b/app/src/main/res/values-land/strings_gls.xml
new file mode 100644
index 000000000..f9504a784
--- /dev/null
+++ b/app/src/main/res/values-land/strings_gls.xml
@@ -0,0 +1,26 @@
+
+
+
+ BGM
+ -126dp
+
diff --git a/app/src/main/res/values-land/strings_hrs.xml b/app/src/main/res/values-land/strings_hrs.xml
new file mode 100644
index 000000000..80558becb
--- /dev/null
+++ b/app/src/main/res/values-land/strings_hrs.xml
@@ -0,0 +1,26 @@
+
+
+
+ HRM
+ -16dp
+
diff --git a/app/src/main/res/values-land/strings_hts.xml b/app/src/main/res/values-land/strings_hts.xml
new file mode 100644
index 000000000..24fb62763
--- /dev/null
+++ b/app/src/main/res/values-land/strings_hts.xml
@@ -0,0 +1,26 @@
+
+
+
+ HTM
+ -18dp
+
diff --git a/app/src/main/res/values-land/strings_proximity.xml b/app/src/main/res/values-land/strings_proximity.xml
new file mode 100644
index 000000000..c3d072ef3
--- /dev/null
+++ b/app/src/main/res/values-land/strings_proximity.xml
@@ -0,0 +1,26 @@
+
+
+
+ PROXIMITY
+ -66dp
+
diff --git a/app/src/main/res/values-land/strings_rsc.xml b/app/src/main/res/values-land/strings_rsc.xml
new file mode 100644
index 000000000..b40b5a41f
--- /dev/null
+++ b/app/src/main/res/values-land/strings_rsc.xml
@@ -0,0 +1,26 @@
+
+
+
+ RSC
+ -13dp
+
diff --git a/app/src/main/res/values-sw600dp-land-v21/dimens.xml b/app/src/main/res/values-sw600dp-land-v21/dimens.xml
new file mode 100644
index 000000000..5b0dcaf3e
--- /dev/null
+++ b/app/src/main/res/values-sw600dp-land-v21/dimens.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ 56dp
+ 0dp
+
+
diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 000000000..0c5384693
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,41 @@
+
+
+
+
+ 24dp
+ 24dp
+ 40dp
+
+ 80dp
+ 80dp
+ 16dp
+ 20dp
+ 256dp
+ 256dp
+
+ -87dp
+
+
diff --git a/app/src/main/res/values-sw600dp/strings_bpm.xml b/app/src/main/res/values-sw600dp/strings_bpm.xml
new file mode 100644
index 000000000..a4b8d6499
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/strings_bpm.xml
@@ -0,0 +1,26 @@
+
+
+
+ BLOOD PRESSURE
+ -115dp
+
diff --git a/app/src/main/res/values-sw600dp/strings_csc.xml b/app/src/main/res/values-sw600dp/strings_csc.xml
new file mode 100644
index 000000000..3fb3e79a9
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/strings_csc.xml
@@ -0,0 +1,26 @@
+
+
+
+ CYCLING SPEED & CADENCE
+ -192dp
+
diff --git a/app/src/main/res/values-sw600dp/strings_dfu.xml b/app/src/main/res/values-sw600dp/strings_dfu.xml
new file mode 100644
index 000000000..a4cd798a4
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/strings_dfu.xml
@@ -0,0 +1,26 @@
+
+
+
+ DEVICE FIRMWARE UPDATE
+ -186dp
+
diff --git a/app/src/main/res/values-sw600dp/strings_gls.xml b/app/src/main/res/values-sw600dp/strings_gls.xml
new file mode 100644
index 000000000..afe00944c
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/strings_gls.xml
@@ -0,0 +1,26 @@
+
+
+
+ GLUCOSE MONITOR
+ -126dp
+
diff --git a/app/src/main/res/values-sw600dp/strings_hrs.xml b/app/src/main/res/values-sw600dp/strings_hrs.xml
new file mode 100644
index 000000000..74df6fc3a
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/strings_hrs.xml
@@ -0,0 +1,26 @@
+
+
+
+ HEART RATE MONITOR
+ -152dp
+
diff --git a/app/src/main/res/values-sw600dp/strings_hts.xml b/app/src/main/res/values-sw600dp/strings_hts.xml
new file mode 100644
index 000000000..c1b401836
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/strings_hts.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ HEALTH THERMOMETER MONITOR
+ -236dp
+
+
diff --git a/app/src/main/res/values-sw600dp/strings_rsc.xml b/app/src/main/res/values-sw600dp/strings_rsc.xml
new file mode 100644
index 000000000..6d741c5f1
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/strings_rsc.xml
@@ -0,0 +1,26 @@
+
+
+
+ RUNNING SPEED & CADENCE
+ -192dp
+
diff --git a/app/src/main/res/values-sw720dp-land/dimens.xml b/app/src/main/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 000000000..db1b9edee
--- /dev/null
+++ b/app/src/main/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ 128dp
+ 32dp
+ 60dp
+
+ 100dp
+ 128dp
+ 20dp
+ 40dp
+
+
diff --git a/app/src/main/res/values-v21/dimens.xml b/app/src/main/res/values-v21/dimens.xml
new file mode 100644
index 000000000..39782e117
--- /dev/null
+++ b/app/src/main/res/values-v21/dimens.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+ 56dp
+ -48dp
+
+ 26dp
+ 18dp
+ 0dp
+
+
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..634ad1ff4
--- /dev/null
+++ b/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/color.xml b/app/src/main/res/values/color.xml
new file mode 100644
index 000000000..55835f205
--- /dev/null
+++ b/app/src/main/res/values/color.xml
@@ -0,0 +1,37 @@
+
+
+
+ #939597
+ #009CDE
+ #0081B7
+ #E6E7E8
+ #DC582A
+ #9E3F1E
+ #EFB5A0
+ #009ada
+ #0081B7
+ #FFFFFF
+ #FFFFFF
+
+ #AAABAF
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..2c070a5a1
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,47 @@
+
+
+
+
+ 16dp
+ 16dp
+ 32dp
+ 16dp
+ 50dp
+ 50dp
+ 8dp
+ 8dp
+ 0dp
+ 256dp
+ 256dp
+
+
+ 10dp
+ 0dp
+ 0dp
+ -81dp
+
+ 16dp
+ 8dp
+ 8dp
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..299a2729e
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,73 @@
+
+
+
+
+ nRF Toolbox
+ fonts/trebucbd.ttf
+ fonts/trebuc.ttf
+
+ read and write log
+ Allows the app to write and read from Nordic Semiconductor Event log
+
+ 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.
+
+
diff --git a/app/src/main/res/values/strings_bpm.xml b/app/src/main/res/values/strings_bpm.xml
new file mode 100644
index 000000000..4d3170e22
--- /dev/null
+++ b/app/src/main/res/values/strings_bpm.xml
@@ -0,0 +1,48 @@
+
+
+
+ BPM
+
+ BLOOD PRESSURE
+ -115dp
+
+ BLOOD PRESSURE
+ SYSTOLIC
+ DIASTOLIC
+ MEAN AP
+ mmHg
+ kPa
+
+ PULSE RATE
+ PULSE
+ bpm
+
+ TIME & DATE
+ %1$tT %1$te.%1$tm.%1$tY
+
+ DEFAULT BPM
+
+ BPM (Blood Pressure Monitor) profile allows you to connect to your Blood Pressure device.
+ It supports the cuff pressure notifications and displays systolic, diastolic and mean arterial pulse values as well as the pulse after blood pressure reading is completed.
+ The implementation supports bondable devices.
+
diff --git a/app/src/main/res/values/strings_csc.xml b/app/src/main/res/values/strings_csc.xml
new file mode 100644
index 000000000..96744efe0
--- /dev/null
+++ b/app/src/main/res/values/strings_csc.xml
@@ -0,0 +1,158 @@
+
+
+
+ CSC
+ CSC Settings
+
+ CYCLING SPEED & CADENCE
+ -192dp
+
+ SPEED AND CADENCE
+ SPEED
+ km/h
+ CADENCE
+ RPM
+ DISTANCE
+ m
+ km
+ TOTAL DISTANCE
+ km
+ GEAR RATIO
+
+ DEFAULT CSC
+ Disconnect
+ %s is connected.
+
+ Wheel size
+ Wheel size
+ Wheel circumference: %s mm
+
+ 60–622
+ 50–622
+ 47–622
+ 44–622
+ 40–635
+ 40–622
+ 38–622
+ 37–622
+ 35–622
+ 32–630
+ 32–622
+ 32–622
+ 28–622
+ 60–559
+ 28–622
+ 25–622
+ 25–622
+ 23–622
+ 20–622
+ 18–622
+ 35–630
+ 32–630
+ 28–630
+ 57–559
+ 54–559
+ 37–590
+ 23–622
+ 50–559
+ 20–622
+ 54–559
+ 47–559
+ 35–590
+ 37–590
+ 47–559
+ 50–559
+ 44–559
+ 40–559
+ 23–571
+ 20–571
+ 32–559
+ 25–571
+ 34–540
+ 50–507
+ 47–507
+ 28–451
+ 50–406
+ 47–406
+ 28–369
+ 35–349
+ 47–305
+
+
+
+ 2340
+ 2284
+ 2268
+ 2224
+ 2265
+ 2224
+ 2180
+ 2205
+ 2168
+ 2199
+ 2174
+ 2155
+ 2149
+ 2146
+ 2136
+ 2146
+ 2105
+ 2133
+ 2114
+ 2102
+ 2169
+ 2161
+ 2155
+ 2133
+ 2114
+ 2105
+ 2097
+ 2089
+ 2086
+ 2114
+ 2070
+ 2068
+ 2105
+ 2055
+ 2089
+ 2051
+ 2026
+ 1973
+ 1954
+ 1953
+ 1952
+ 1948
+ 1910
+ 1907
+ 1618
+ 1593
+ 1590
+ 1325
+ 1282
+ 1272
+
+
+ CSC (Cycling Speed and Cadence) profile allows you to connect to your bike activity sensor.
+ \nIt reads wheel and crank data if they are supported by the sensor and calculates speed, cadence, total and trip distance and gear ratio. Set up your wheel size in the settings to get correct readings.
+ \nYou may exit the application when connected while still getting notifications.
+
diff --git a/app/src/main/res/values/strings_dfu.xml b/app/src/main/res/values/strings_dfu.xml
new file mode 100644
index 000000000..edbc155c2
--- /dev/null
+++ b/app/src/main/res/values/strings_dfu.xml
@@ -0,0 +1,119 @@
+
+
+
+ DFU
+ nRF Toolbox DFU Proxy
+ DFU Settings
+
+ DEVICE FIRMWARE UPDATE
+ -186dp
+
+ Example HEX files were copied to /sdcard/Nordic Semiconductor.
+ New example HEX files were copied to /sdcard/Nordic Semiconductor.
+ DFU script files were copied to /sdcard/Nordic Semiconductor.
+
+ APPLICATION
+ File Name:
+ File Type:
+ File Size:
+ Status:
+ %d bytes
+ OK
+ OK (Init file selected)
+ File not loaded
+ Invalid file
+ Reading file failed
+ Please, select valid HEX file
+ Info
+
+ SELECT FILE
+
+ File Browser not found
+ File browser application has not been found on your device. Would you like to download one?
+
+ Drive
+ File Manager
+ Total Commander
+ Search for others
+
+
+ market://details?id=com.google.android.apps.docs
+ market://details?id=com.rhmsoft.fm
+ market://details?id=com.ghisler.android.TotalCommander
+ market://search?q=file manager
+
+
+ DEVICE FIRMWARE UPDATE
+ UPLOAD
+ \?
+ CANCEL
+
+ DFU options
+ Packets receipt notification procedure
+ Number of packets
+ MBR size
+ MBR size (default 4096 = 0x1000)
+ About DFU
+ DFU documentation on Nordic\'s Developer Zone
+ Information
+ During a DFU operation a lot of data packets are being sent to the target. The onCharacteristicWrite(...) callback
+ in Android API is being invoked when the data has been written to the outgoing queue, not when physically sent. Packet Receipt Notifications were introduced to prevent from
+ overflowing the queue. Disabling them or setting the value to high (> ~300) may cause the DFU process to freeze at some point, depending on the device model.
+
+ Select file type
+
+ Soft Device
+ Bootloader
+ Application
+ Multiple files (ZIP)
+
+ Init packet
+ Do you want to select the Init packet file?\n
+ \nThe Init packet file (*.dat) should contain the device type and revision, application version, list of supported Soft Devices and the firmware CRC in binary format or, in case of old
+ versions of the DFU bootloader, only the CRC (CRC-CCITT-16). In case of the new version of the bootloader the extended Init packet is required.
+
+ unnamed device
+ 0%
+ %d%%
+ DEFAULT DFU
+ Application Uploading
+ Are you sure to exit?
+ Are you sure to cancel upload?
+ Application has been transferred successfully.
+ Uploading of the application has been canceled.
+
+ Select file
+ A file browser application must be installed on the device before selecting the file.
+ \n\nThere are number of applications available on Google Play store, f.e. Total Commander or File Manager, that allow you to pick a file from internal memory of the device. To upload
+ a file from the Internet you may use f.e. Drive or Dropbox application.
+ \n\nYou will be asked to select an application if more than one is installed. A single application will be launched automatically.
+ \n\nSince Android KitKat you may use the preinstalled document picker application. Ensure Display advanced devices option is enabled in settings to use the Internal storage.
+ \n\nHeart Rate Monitor and Running Speed and Cadence applications were copied to Nordic Semiconductor folder in the internal storage.
+
+ With the DFU you may update your application, Soft Device or the Booloader Over-The-Air. For each type a HEX or a BIN file may be provided.
+ \nIn order to update the Soft Device together with a compatible Bootloader put them into a ZIP file and name according to the image below.
+ \nYou may also provide the Init packet(s) that will be send to verify the firmware on the device side. More information about the Init packet is in the DFU documentation.
+ The Device Firmware Update (DFU) app allows users to update the firmware of their Bluetooth Smart device over-the-air (OTA). It is compatible with
+ Nordic Semiconductor nRF51822 or nRF51422 devices with the S110 SoftDevice and DFU bootloader enabled. With the SoftDevice 7.0.0+ a SoftDevice itself and/or a bootloader may also be updated.
+ \n\nFor more information about the DFU see the About DFU section in Settings.
+
diff --git a/app/src/main/res/values/strings_gls.xml b/app/src/main/res/values/strings_gls.xml
new file mode 100644
index 000000000..e233fdff4
--- /dev/null
+++ b/app/src/main/res/values/strings_gls.xml
@@ -0,0 +1,163 @@
+
+
+
+
+ BGM
+ GLUCOSE MONITOR
+
+ -126dp
+
+ DEFAULT BGM
+ READINGS
+ %1$te %1$tb %1$tY at %1$tT
+ No values to display
+ CONTROL
+ All
+ Last
+ More
+ Abort
+ Refresh
+ First
+ Clear
+ Delete all
+
+
+ Reserved for future use
+ Capillary Whole blood
+ Capillary Plasma
+ Venous Whole blood
+ Venous Plasma
+ Arterial Whole blood
+ Arterial Plasma
+ Undetermined Whole blood
+ Undetermined Plasma
+ Interstitial Fluid (ISF)
+ Control Solution
+
+ Sample Location
+
+ Reserved for future use
+ Finger
+ Alternate Site Test (AST)
+ Earlobe
+ Control solution
+
+
+
+
+
+
+
+
+
+
+ Sample Location value not available
+
+ Sensor Status Annunciation
+
+ Device battery low at time of measurement
+ Sensor malfunction or faulting at time of measurement
+ Sample size for blood or control solution insufficient at time of measurement
+ Strip insertion error
+ Strip type incorrect for device
+ Sensor result higher than the device can process
+ Sensor result lower than the device can process
+ Sensor temperature too high for valid test/result at time of measurement
+ Sensor temperature too low for valid test/result at time of measurement
+ Sensor read interrupted because strip was pulled too soon at time of measurement
+ General device fault has occurred in the sensor
+ Time fault has occurred in the sensor and time may be inaccurate
+
+ Carbohydrate
+
+ Reserved for future use
+ Breakfast
+ Lunch
+ Dinner
+ Snack
+ Drink
+ Supper
+ Brunch
+
+ Meal
+
+ Reserved for future use
+ Preprandial (before meal)
+ Postprandial (after meal)
+ Fasting
+ Casual (snacks, drinks, etc.)
+ Bedtime
+
+ Tester
+
+ Reserved for future use
+ Self
+ Health Care Professional
+ Lab test
+
+
+
+
+
+
+
+
+
+
+
+ Tester value not available
+
+ Health
+
+ Reserved for future use
+ Minor health issues
+ Major health issues
+ During menses
+ Under stress
+ No health issues
+
+
+
+
+
+
+
+
+
+ Health value not available
+
+
+
+ Downloading %d records
+ Operation not supported by the device
+ Operation failed
+ %.1f
+ mg/dl
+ mmol/l
+
+ GLUCOSE SERVICE profile allows you to connect to your Glucose sensor.
+ You can read the last and historical glucose measurement readings from the device. The current implementation partially supports also the
+ Glucose Measurement Context characteristic which is not supported by the GLS sample in the NRF51 SDK,
+ therefore when connecting to this sample application some parameters are not displayed.
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_hrs.xml b/app/src/main/res/values/strings_hrs.xml
new file mode 100644
index 000000000..4da0a7f68
--- /dev/null
+++ b/app/src/main/res/values/strings_hrs.xml
@@ -0,0 +1,46 @@
+
+
+
+ HRM
+
+ HEART RATE MONITOR
+ -152dp
+
+ DEFAULT HRM
+ bpm
+ sensor position
+
+
+ Other
+ Chest
+ Wrist
+ Finger
+ Hand
+ Ear Lobe
+ Foot
+
+ Reserved
+
+ HRM (Heart Rate Monitor) profile allows you to connect to your Heart Rate sensor (f.e. a belt).
+ It shows you the current rate, location of the sensor and a historic data on the graph.
+
diff --git a/app/src/main/res/values/strings_hts.xml b/app/src/main/res/values/strings_hts.xml
new file mode 100644
index 000000000..fe328bfa0
--- /dev/null
+++ b/app/src/main/res/values/strings_hts.xml
@@ -0,0 +1,37 @@
+
+
+
+ HTM
+
+ HEALTH THERMOMETER MONITOR
+ -236dp
+
+ DEFAULT HTM
+ °C
+
+ Disconnect
+ %s is connected.
+
+ HTM (Health Thermometer Monitor) profile allows you to connect to your Health Thermometer sensor.
+ It shows you the temperature value in Celsius.
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_proximity.xml b/app/src/main/res/values/strings_proximity.xml
new file mode 100644
index 000000000..8990f1964
--- /dev/null
+++ b/app/src/main/res/values/strings_proximity.xml
@@ -0,0 +1,42 @@
+
+
+
+ PROXIMITY
+
+ PROXIMITY MONITOR
+ -142dp
+
+ DEFAULT PROXIMITY
+ FindMe
+ SilentMe
+ LOCK Image
+ Disconnect
+ %s is getting away!
+ %s is connected.
+
+ GATT Server enabled
+
+ PROXIMITY profile allows you to connect to your Proximity sensor.
+ \nYou can find your valuables attached with Proximity tag by pressing FindMe button on screen and you can find your phone by pressing relevant button on your tag.
+ \n\nYou may close this activity when connected without being disconnected from the sensor. A notification will be shown.
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_rsc.xml b/app/src/main/res/values/strings_rsc.xml
new file mode 100644
index 000000000..8aec5806b
--- /dev/null
+++ b/app/src/main/res/values/strings_rsc.xml
@@ -0,0 +1,53 @@
+
+
+
+ RSC
+
+ RUNNING SPEED & CADENCE
+ -192dp
+
+ SPEED AND CADENCE
+ SPEED
+ km/h
+ CADENCE
+ SPM
+ DISTANCE
+ m
+ km
+
+ TOTAL DISTANCE
+ km
+ STEPS
+
+ ACTIVITY
+ WALKING
+ RUNNING
+
+ DEFAULT RSC
+ Disconnect
+ %s is connected.
+
+ RSC (Running Speed and Cadence) profile allows you to connect to your activity sensor.
+ \nIt reads speed and cadence values from the sensor and calculates trip distance if stride length is supported. Strides count is calculated basing on the cadence and the time.
+ \nYou may exit the application when connected while still getting notifications.
+
diff --git a/app/src/main/res/values/strings_uart.xml b/app/src/main/res/values/strings_uart.xml
new file mode 100644
index 000000000..dbafb24e4
--- /dev/null
+++ b/app/src/main/res/values/strings_uart.xml
@@ -0,0 +1,52 @@
+
+
+
+ UART
+
+ EDIT
+ DONE
+ Write command
+ SEND
+ Show log
+
+ UART
+ -24dp
+
+ DEFAULT UART
+ No data to display.
+
+ Configure button
+ Enter command
+ Command must not be empty
+ Active
+ Select icon:
+
+ Disconnect
+ %s is connected.
+
+ 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
+ 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
new file mode 100644
index 000000000..02b3ea1e9
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/settings_csc.xml b/app/src/main/res/xml/settings_csc.xml
new file mode 100644
index 000000000..8dd3d543c
--- /dev/null
+++ b/app/src/main/res/xml/settings_csc.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/settings_dfu.xml b/app/src/main/res/xml/settings_dfu.xml
new file mode 100644
index 000000000..75ac0e6db
--- /dev/null
+++ b/app/src/main/res/xml/settings_dfu.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..6356aabdc
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.0.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 000000000..1d3591c8a
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..8c0fb64a8
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..0c71e760d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 000000000..91a7e269e
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 000000000..8a0b282aa
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/nRFToolbox.iml b/nRFToolbox.iml
new file mode 100644
index 000000000..2a0220140
--- /dev/null
+++ b/nRFToolbox.iml
@@ -0,0 +1,21 @@
+
+
+
+
+
+