Initial release based on BLE Stack 2_2_0 master
authorJarle Boe <j.boe@ti.com>
Mon, 16 Jan 2017 11:49:59 +0000 (12:49 +0100)
committerJarle Boe <j.boe@ti.com>
Mon, 16 Jan 2017 11:49:59 +0000 (12:49 +0100)
37 files changed:
BLE OAD Android Source Code 1_0 manifest.html [new file with mode: 0644]
app/build.gradle [new file with mode: 0644]
app/build/outputs/apk/app-debug.apk [new file with mode: 0644]
app/src/main/AndroidManifest.xml [new file with mode: 0644]
app/src/main/java/com/example/ti/oadexample/FileActivity.java [new file with mode: 0644]
app/src/main/java/com/example/ti/oadexample/FwUpdateActivity.java [new file with mode: 0644]
app/src/main/java/com/example/ti/oadexample/MainActivity.java [new file with mode: 0644]
app/src/main/java/com/example/ti/oadexample/OadProcess.java [new file with mode: 0644]
app/src/main/java/com/example/ti/oadexample/TIOADProfile.java [new file with mode: 0644]
app/src/main/java/com/example/ti/oadexample/Util.java [new file with mode: 0644]
app/src/main/res/drawable/gradient_bg.xml [new file with mode: 0644]
app/src/main/res/drawable/gradient_pressed_bg.xml [new file with mode: 0644]
app/src/main/res/drawable/group_box.xml [new file with mode: 0644]
app/src/main/res/drawable/list_border.xml [new file with mode: 0644]
app/src/main/res/drawable/states_selector_list.xml [new file with mode: 0644]
app/src/main/res/layout/activity_file.xml [new file with mode: 0644]
app/src/main/res/layout/activity_fw_update.xml [new file with mode: 0644]
app/src/main/res/layout/activity_main.xml [new file with mode: 0644]
app/src/main/res/layout/content_file.xml [new file with mode: 0644]
app/src/main/res/layout/content_fw_update.xml [new file with mode: 0644]
app/src/main/res/layout/content_main.xml [new file with mode: 0644]
app/src/main/res/layout/element_file.xml [new file with mode: 0644]
app/src/main/res/mipmap-hdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/mipmap-mdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/mipmap-xhdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/mipmap-xxhdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/values/colors.xml [new file with mode: 0644]
app/src/main/res/values/dimens.xml [new file with mode: 0644]
app/src/main/res/values/strings.xml [new file with mode: 0644]
app/src/main/res/values/styles.xml [new file with mode: 0644]
build.gradle [new file with mode: 0644]
gradle/wrapper/gradle-wrapper.jar [new file with mode: 0644]
gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
gradlew [new file with mode: 0644]
gradlew.bat [new file with mode: 0644]
settings.gradle [new file with mode: 0644]

diff --git a/BLE OAD Android Source Code 1_0 manifest.html b/BLE OAD Android Source Code 1_0 manifest.html
new file mode 100644 (file)
index 0000000..16f56f1
--- /dev/null
@@ -0,0 +1,328 @@
+<!--\r\r
+Texas Instruments Manifest Format 2.0\r\r
+-->\r\r
+\r\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">\r\r
+<html>\r\r
+\r\r
+<head>\r\r
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />\r\r
+<!-- @Start Style -->\r\r
+<!-- Default style in case someone doesnt have Internet Access -->\r\r
+<style type="text/css" id="internalStyle">\r\r
+       body, div, p {\r\r
+               font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif;\r\r
+               font-size: 13px;\r\r
+               line-height: 1.3;\r\r
+       }\r\r
+       body {\r\r
+               margin: 20px;   \r\r
+       }\r\r
+       h1 {\r\r
+               font-size: 150%;\r\r
+       }\r\r
+       h2 {\r\r
+               font-size: 120%;\r\r
+       }\r\r
+       h3 {\r\r
+               font-size: 100%;\r\r
+       }\r\r
+       img {\r\r
+               border: 0px;\r\r
+               vertical-align: middle;\r\r
+       }\r\r
+       table, th, td, tr {\r\r
+               border: 1px solid black;        \r\r
+               font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif;\r\r
+               font-size: 13px;\r\r
+               line-height: 1.3;\r\r
+               empty-cells: show;  \r\r
+               padding: 5px;\r\r
+       }\r\r
+       table {\r\r
+               border-collapse: collapse; \r\r
+               width: 100%;\r\r
+       }\r\r
+       tr {\r\r
+               page-break-inside: avoid;\r\r
+       }\r\r
+       #TIlogoLeft {\r\r
+               background-color: black; \r\r
+               padding: 0;\r\r
+               width: 20%;\r\r
+       }\r\r
+       #TIlogoRight {\r\r
+               background-color: red; \r\r
+               padding: 0;\r\r
+       }\r\r
+       #ProductName {\r\r
+               text-align: center;\r\r
+       }\r\r
+       #ReleaseDate {\r\r
+               text-align: center;\r\r
+       }\r\r
+       .LogoSection {\r\r
+               margin: 0;\r\r
+               padding: 0;\r\r
+       }\r\r
+       .HeaderSection {\r\r
+               margin: 25px 0 25px 0;\r\r
+               padding: 0;\r\r
+       }\r\r
+       .LegendSection {\r\r
+               margin: 25px 0 25px 0;\r\r
+       }\r\r
+       .ExportSection {\r\r
+               margin: 25px 0 25px 0;\r\r
+       }\r\r
+       .DisclaimerSection {\r\r
+               margin: 25px 0 25px 0;  \r\r
+       }\r\r
+       .CreditSection {\r\r
+               margin: 25px 0 25px 0;  \r\r
+       }\r\r
+       .LicenseSection {\r\r
+               margin: 25px 0 25px 0;  \r\r
+       }\r\r
+       .ManifestTable {\r\r
+               margin: 25px 0 25px 0;  \r\r
+       }\r\r
+</style> \r\r
+<!-- Override style from TI if they have Internet Access -->\r\r
+<link type="text/css" rel="stylesheet" href="timanifeststyle.css">\r\r
+<!-- @End Style -->\r\r
+<title>Texas Instruments Manifest</title>\r\r
+</head>\r\r
+\r\r
+<body><!-- Logo display, will need to fix up the URLs, this is just for testing.. Image alternate display not wporking well yet -->\r\r
+<div class="LogoSection">\r\r
+<table>\r\r
+  <tbody>\r\r
+    <tr>\r\r
+      <td id="TIlogoLeft">\r\r
+        <a href="http://www.ti.com/">\r\r
+          <!-- img src="tilogo.gif" alt="Texas Instruments Incorporated" -->\r\r
+                 <img alt="" src="data:image/gif;base64,R0lGODlh3gA2AKIAAAAAAP///7u7u29vbz8/PwYGBujo6BgYGCH5BAAAAAAALAAAAADeADYAAAP/CLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodHorDALYLIHKJVqz2q44eAUHtoDB4DBu48rgLQErcNtnX7NhMDcICIB3gix5ZmtqAAZZew8EAo+QkQIDNVZqiIM1cHGKZ4YPAmaiAWw0c1gFmZqjB3SbZ6kNe6WhsAeOlDV0qjSFAXUAp7lwuREFtVsFgMvLB7fNAM+BCs+lDLd8BNYOuxfV22PL0RiWlwO1u3kDqejAEsjR6GB86FsHoYwA6gxWnVgGEegUuIelWJk6jswAGlXQ36J1xBSoQwfulIEDr/6l+VeK/+AehrAGOHRnAWRBbbWegckXAV6wk4AeRQtDQBEaBYsYlMl2hUCsBt0iKgilT9EfAlfO7SmzdKkrkQUT/fqZSECqLCSlntH375IAA1tqGUilLIBSNVnU+NmJNBRVChlF1QwAdlRWBy5P3QymwCLBYhs73cTHYBq3X33nDQ2wcWuBgef0FRD4GK3jU3VCZZUJAIw1OGg0P+4bFiubOWoOsEP1+KvZn3wurDbZ6lfcuw3yYkFjRSeYzRe7ARAbW0K3PmGIMi0OFDG1Mmha+RnufAHn3xL9ha6uTZ/rXagZ1GKAtTsHeWb+FEQvHILuX4+mLzj2j2r4TrFesTwMbE5Cuv8JzbTSGuRV1xgfUJFC3WbA0JWFalcItpgf8YU2yT/qATaedent5cBb8zk0DzIitgfKbonRFV9Wp2xl3UXq5Ccibp05598BnRigiAIJmrZAexkJQIuBwzX4CB3SQbeYQkPVAUco63DI2HzsAdYAiAvEZdYlaVQ5wXs3+bQAjovEUoBRR9LVAFLaPXCcY/KMqVRasQB5kiJgLcYgTkJiuCWKC2ZpIY/z/LRhYefkBAGW1HTyRy2UjObLHxSAOZ948EUVGCSC3SLZbB7iZKOLc2GRRgMH/VhdHnJwFCgD8iEGx0VKvpqbO+hoaCppEg3UiTES1CTkhNaQ+Qs4LQGql07/lET4mIQ6SvTSVGZ9Bmhz/bkYzK+PFKtpje6wumRm1wrLZzSdQASoZvyswdmSuk7p616HfkjBTxZBQucFgqXCFKdn1NpiUlQJhs8kteBWG0AbATbXS2tBlaeoVkmJRova4KkGPmhMFdiSYmq8cbTRYhrlkiHaNufJ9mIgVqEXnAOJM5JE4sgjudQ8bF82x+cKBP4Iiedecyjgx2/WtMNjjhcL9h+S4xq9RYJgsbeeUbmdrPTSQbPccsyijEXOfI8xyuinVJH1wdkS/MQ2Bc5Iq08DyHYwGglvPyCilbz0fa8GLV7r9+Btb7CJ14Qnzg8HpdKoOOF5Py752JNXvrblNphzEHnmnF/a+ecTbA465qKPXnnppkuOeuqKr8465K+z7nrsfc9Ouyq23z5I7rrfwXvvbhSQAAA7" />\r\r
+        </a>\r\r
+      </td>\r\r
+      <td id="TILogoRight">\r\r
+        <!-- img src="titagline.gif" alt="Technology for Innovators(tm)"-->\r\r
+               <img alt="" src="data:image/gif;base64,R0lGODlhOgEaALMAAP8AAP////92dv+3t/+Njf/W1v/t7f8hIf/19f+jo//Hx/8/P/9cXP/j4//6+v/+/iH5BAAAAAAALAAAAAA6ARoAAAT/EMhJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqft0NDMCBQodis1jcADBKE7nYcCpjPgU5AQBKkVYOHAeRudqtXsh60/vRHdSoBBCGBNAkLe4o4f2psgG8pjR6GM5OLmDB/DA0GBoQADAgICRIBBQUOYgwGCg2kEgudBgUHAIGcBg0MsZ0NCnMGYgsBtqEGAbCynrW3AQONgcIFBgiErK6wAAfUtLbCscWiowoAyLDczLZu0AIJCAYOoJn0G38ObAwPEvLEts/O1vUhsA8AAjGonEmA9W6hGAVpEjiQoKBAhT8HJSRkVyEQQAAJ//a5YeMPQIFyACqCnJjSIgFCB4oB+HOSokWOAB6wIWCxnk8MfYh5QsYg5sVHfQLVMSqhztJIxWIaC6QzJy8KfZgqrNT0zR+nUNl8fSMvZ6IDwJCJRfoI7IR4Cub9nDsha6RwR02xUZpGq1utUWUq9FKgYV6/abgOHjt45tquEgY0SDDHoJg+fxhXolKNrmfH/EoR5EdAKmjQfB1qvPmGIQIJ3g4gC2egVF7LqxtP8Ng2cViTKFUCIGbNFKEEmB/VbDlYdqLRn+du8oTg6jjbmfe+CbTM2+BcuySgbQVtQoOCt7s3U8wbsqGs3ZppZLnylwFe8Uql825ogANPckUnYDoOCogxQGXADajggjcw4AA8DSSyTQASMmjhhTQscBWGHHbo4YcghijiiCSWaOKJKKao4oostugiFBEAADs=" />\r\r
+      </td>\r\r
+    </tr>\r\r
+  </tbody>\r\r
+</table>\r\r
+</div><div class="HeaderSection">\r\r
+<h1 id="ProductName">\r\r
+<!-- @Start Product -->\r\r
+BLE OAD Android Source Code Manifest\r\r
+<!-- @End Product -->\r\r
+</h1>\r\r
+\r\r
+<h2 id="ReleaseDate">\r\r
+<!-- @Start Date -->\r\r
+12-16-2016\r\r
+<!-- @End Date -->\r\r
+</h2>\r\r
+\r\r
+\r\r
+<h2 id="SRASID">\r\r
+<!-- @Start Date -->\r\r
+Manifest ID - SRAS00003564\r\r
+<!-- @End Date -->\r\r
+</h2>\r\r
+</div><div class="LegendSection">\r\r
+<h2>Legend</h2>\r\r
+<p>(explanation of the fields in the Manifest Table below)</p>\r\r
+<table>\r\r
+<tbody>\r\r
+<tr>\r\r
+<td>\r\r
+<b>Software Name </b>\r\r
+</td>\r\r
+<td>\r\r
+The name of the application or file\r\r
+</td>\r\r
+</tr>\r\r
+<tr>\r\r
+<td>\r\r
+<b>Version</b>\r\r
+</td>\r\r
+<td>\r\r
+Version of the application or file\r\r
+</td>\r\r
+</tr>\r\r
+<tr>\r\r
+<td>\r\r
+<b>License Type</b>\r\r
+</td>\r\r
+<td>\r\r
+Type of license(s) under which TI will be providing\r\r
+software to the licensee (e.g. BSD-3-Clause, GPL-2.0, TI TSPA License, TI\r\r
+Commercial License). The license could be under Commercial terms or Open Source. See Open Source Reference License Disclaimer in\r\r
+the Disclaimers Section. Whenever possible, TI will use an <a href="http://spdx.org/licenses/"> SPDX Short Identifier </a> for an Open Source\r\r
+License. TI Commercial license terms are not usually included in the manifest and are conveyed through a variety \r\r
+of means such as a clickwrap license upon install, \r\r
+a signed license agreement and so forth.\r\r
+</td>\r\r
+</tr>\r\r
+<tr>\r\r
+<td>\r\r
+<b>Location</b>\r\r
+</td>\r\r
+<td>\r\r
+The directory name and path on the media or a specific file where the Software is located. Typically fully qualified path names \r\r
+are not used and instead the relevant top level directory of the application is given. \r\r
+A notation often used in the manifests is [as installed]/directory/*. Note that the asterisk implies that all\r\r
+files under that directory are licensed as the License Type field denotes. Any exceptions to this will \r\r
+generally be denoted as [as installed]/directory/* except as noted below which means as shown in subsequent rows of \r\r
+the manifest.\r\r
+</td>\r\r
+</tr>\r\r
+<tr>\r\r
+<td>\r\r
+<b>Delivered As</b>\r\r
+</td>\r\r
+<td>\r\r
+This field will either be &#8220;Source&#8221;, &#8220;Binary&#8221; or &#8220;Source\r\r
+and Binary&#8221; and is the primary form the content of the Software is delivered\r\r
+in. If the Software is delivered in an archive format, this field\r\r
+applies to the contents of the archive. If the word Limited is used\r\r
+with Source, as in &#8220;Limited Source&#8221; or &#8220;Limited Source and Binary&#8221; then\r\r
+only portions of the Source for the application are provided.\r\r
+</td>\r\r
+</tr>\r\r
+<tr>\r\r
+<td>\r\r
+<b>Modified by TI</b>\r\r
+</td>\r\r
+<td>\r\r
+This field will either be &#8220;Yes&#8221; or &#8220;No&#8221;. A &#8220;Yes&#8221; means\r\r
+TI has made changes to the Software. A &#8220;No&#8221; means TI has not made any\r\r
+changes. Note: This field is not applicable for Software &#8220;Obtained\r\r
+from&#8221; TI.\r\r
+</td>\r\r
+</tr>\r\r
+<tr>\r\r
+<td>\r\r
+<b>Obtained from</b>\r\r
+</td>\r\r
+<td>\r\r
+This field specifies from where or from whom TI obtained\r\r
+the Software. It may be a URL to an Open Source site, a 3<sup>rd</sup>\r\r
+party licensor, or TI. See Links Disclaimer in the Disclaimers\r\r
+Section.\r\r
+</td>\r\r
+</tr>\r\r
+</tbody>\r\r
+</table>\r\r
+</div><div class="DisclaimerSection">\r\r
+<h2>Disclaimers</h2>\r\r
+<h3>Export Control Classification Number (ECCN)</h3>\r\r
+<p>Any use of ECCNs listed in the Manifest is at the user&#8217;s risk\r\r
+and without recourse to TI. Your\r\r
+company, as the exporter of record, is responsible for determining the\r\r
+correct classification of any item at\r\r
+the time of export. Any export classification by TI of Software is for\r\r
+TI&#8217;s internal use only and shall not be construed as a representation\r\r
+or warranty\r\r
+regarding the proper export classification for such Software or whether\r\r
+an export\r\r
+license or other documentation is required for exporting such Software</p>\r\r
+<h3>Links in the Manifest</h3>\r\r
+<p>Any\r\r
+links appearing on this Manifest\r\r
+(for example in the &#8220;Obtained from&#8221; field) were verified at the time\r\r
+the Manifest was created. TI makes no guarantee that any listed links\r\r
+will\r\r
+remain active in the future.</p>\r\r
+<h3>Open Source License References</h3>\r\r
+<p>Your company is responsible for confirming the\r\r
+applicable license terms for any open source Software\r\r
+listed in this Manifest that was not &#8220;Obtained from&#8221; TI. Any open\r\r
+source license\r\r
+specified in this Manifest for Software that was\r\r
+not &#8220;Obtained from&#8221; TI is for TI&#8217;s internal use only and shall not be\r\r
+construed as a representation or warranty regarding the proper open\r\r
+source license terms\r\r
+for such Software.</p>\r\r
+</div><div class="ExportSection">\r\r
+<h2>Export Information</h2>\r\r
+<p>ECCN for Software included in this release:</p>\r\r
+Publicly Available  - Open Source or TI TSPA License\r\r
+</div><div class="ManifestTable">\r\r
+<!-- h2>Manifest Table</h2 -->\r\r
\r
+ <table> \r
+ <tbody> \r
\r
+ <h2> \r
+  BLE OAD Android Source Code Manifest Table \r
+ </h2> \r
\r
+  \r
+ <p> \r
\r
+ See the Legend above for a description of these columns. \r
\r
+ </p> \r
+  \r
+ <table id="targetpackages" name="targetpackages"> \r
+ <thead>  \r
+       <tr> \r
+               <td><b>Software Name</b></td> \r
+               <td><b>Version</b></td> \r
+               <td><b>License Type</b></td> \r
+               <td><b>Delivered As</b></td> \r
+               <td><b>Modified by TI</b></td> \r
+               <td></td> \r
+               <td></td> \r
+       </tr> \r
+ </thead>  \r
\r
\r
+ <tbody> \r
+       <tr> \r
+               <td id="name" name="name" rowspan="2"> \r
+ BLE OAD Code Example \r
+ </td> \r
+               <td id="version" name="version" rowspan="2"> \r
+ 1.0 \r
+ </td> \r
+               <td id="license" name="license" rowspan="2"> \r
+ BSD-3-Clause \r
+ </td> \r
+               <td id="delivered" name="delivered" rowspan="2"> \r
+ Source and Binary \r
+ </td> \r
+               <td id="modified" name="modified" rowspan="2"> \r
+ N/A \r
+ </td> \r
+               <td><b>Location</b></td> \r
+               <td id="location" name="location"> \r
+ [as installed]/bin \r
+ </td> \r
+       </tr> \r
+       <tr> \r
+               <td><b>Obtained from</b></td> \r
+               <td id="obtained" name="obtained"> \r
+ TI \r
+ </td> \r
+       </tr> \r
\r
+ </tbody> \r
+ </table> \r
+  \r
+ </p> \r
+ </p> \r
+ <p> \r
+\r\r
+</div><div class="CreditSection">\r\r
+<h2>Credits</h2>\r\r
+<BR> <BR><BR><BR><BR>\r\r
+</div><div class="LicenseSection">\r\r
+<h2>Licenses</h2>\r\r
+<BR><h3><b> BLE OAD Android Source Code Licenses </b></h3><BR> <BR>Copyright (c) 2016, Texas Instruments Incorporated<BR>All rights reserved.<BR>Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:<BR>Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.<BR>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.<BR>Neither the name of the Texas Instruments Incorporated nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.<BR>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.<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>\r\r
+</div>\r\r
+\r\r
+</body></html>
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644 (file)
index 0000000..15e2d0d
--- /dev/null
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "23.0.3"
+
+    defaultConfig {
+        applicationId "com.example.ti.oadexample"
+        minSdkVersion 21
+        targetSdkVersion 23
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    testCompile 'junit:junit:4.12'
+    compile 'com.android.support:appcompat-v7:23.3.0'
+    compile 'com.android.support:design:23.3.0'
+}
diff --git a/app/build/outputs/apk/app-debug.apk b/app/build/outputs/apk/app-debug.apk
new file mode 100644 (file)
index 0000000..7acdabf
Binary files /dev/null and b/app/build/outputs/apk/app-debug.apk differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..e35f982
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.ti.oadexample">
+
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".FwUpdateActivity"
+            android:label="@string/title_activity_fw_update"
+            android:parentActivityName=".MainActivity"
+            android:theme="@style/AppTheme.NoActionBar"
+            android:configChanges="orientation|screenSize"/>
+        <activity
+            android:name=".FileActivity"
+            android:label="@string/title_activity_file"
+            android:theme="@style/AppTheme.NoActionBar"
+            android:windowSoftInputMode="stateHidden" ></activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/app/src/main/java/com/example/ti/oadexample/FileActivity.java b/app/src/main/java/com/example/ti/oadexample/FileActivity.java
new file mode 100644 (file)
index 0000000..bd01be4
--- /dev/null
@@ -0,0 +1,260 @@
+package com.example.ti.oadexample;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.v4.widget.TextViewCompat;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class FileActivity extends AppCompatActivity {
+
+    public final static String EXTRA_FILENAME = "com.example.ti.oadexample.FILENAME";
+    private static final String TAG = "FileActivity";
+
+    // GUI
+    private FileAdapter mFileAdapter;
+    private ListView mLvFileList;
+    private EditText mEtDirName;
+    private Button mBtnConfirm;
+
+    // Housekeeping
+    private String mSelectedFile;
+    private List<String> mFileList;
+    private File mDir;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_file);
+
+        // Set default directory
+        mDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+
+        // Initialize GUI elements
+        mEtDirName = (EditText) findViewById(R.id.et_directory);
+        mBtnConfirm = (Button) findViewById(R.id.btn_confirm);
+        mLvFileList = (ListView) findViewById(R.id.lv_file);
+        mLvFileList.setOnItemClickListener(mFileClickListener);
+
+        // Display path in GUI
+        mEtDirName.setText(mDir.getAbsolutePath());
+        mEtDirName.setSelection(mEtDirName.getText().length());
+
+        // Display files found in path
+        populateFileList();
+
+    }
+    @Override
+    public void onDestroy() {
+        mFileList = null;
+        mFileAdapter = null;
+        super.onDestroy();
+    }
+
+
+    /**
+     * Function called when the user reloads the file directory
+     */
+    public void onDirChanged(View view) {
+        // Save the new directory and populate the list view
+        mDir = new File(mEtDirName.getText().toString());
+        populateFileList();
+    }
+
+    /**
+     * Listener for file click
+     */
+    private OnItemClickListener mFileClickListener = new AdapterView.OnItemClickListener() {
+        public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
+
+            // A file item has been selected
+            mFileAdapter.setSelectedPosition(pos);
+        }
+    };
+
+    /**
+     * Callback for confirm button
+     */
+    public void onConfirm(View v) {
+        Intent i = new Intent();
+
+        // Save selected file (if any) and finish activity
+        if (mFileList.size() > 0) {
+            i.putExtra(EXTRA_FILENAME, mDir.getAbsolutePath() + File.separator + mSelectedFile);
+            setResult(RESULT_OK, i);
+        } else {
+            setResult(RESULT_CANCELED, i);
+        }
+        finish();
+    }
+
+    /**
+    * FileAdapter class: Handle the file list
+    */
+    class FileAdapter extends BaseAdapter {
+        Context mContext;
+        List<String> mFiles;
+        LayoutInflater mInflater;
+        int mSelectedPos;
+
+        public FileAdapter(Context context, List<String> files) {
+            mInflater = LayoutInflater.from(context);
+            mContext = context;
+            mFiles = files;
+            mSelectedPos = 0;
+        }
+
+        @Override
+        public int getCount() {
+            return mFiles.size();
+        }
+
+        @Override
+        public Object getItem(int pos) {
+            return mFiles.get(pos);
+        }
+
+        @Override
+        public long getItemId(int pos) {
+            return pos;
+        }
+
+        @Override
+        public View getView(int pos, View view, ViewGroup parent) {
+            ViewGroup vg;
+
+            if (view != null) {
+                vg = (ViewGroup) view;
+            } else {
+                vg = (ViewGroup) mInflater.inflate(R.layout.element_file, null);
+            }
+
+            // Grab file object
+            String file = mFiles.get(pos);
+
+            // Show file name
+            TextView tvName = (TextView) vg.findViewById(R.id.name);
+            tvName.setText(file);
+
+            // Highlight selected object
+            if (pos == mSelectedPos) {
+                TextViewCompat.setTextAppearance(tvName, R.style.nameStyleSelected);
+            } else {
+                TextViewCompat.setTextAppearance(tvName, R.style.nameStyle);
+            }
+
+            return vg;
+        }
+
+        /**
+         * Function called when a file has been selected.
+         */
+        public void setSelectedPosition(int pos) {
+            mSelectedFile = mFileList.get(pos);
+            mSelectedPos = pos;
+            notifyDataSetChanged();
+        }
+
+    }
+
+    /**
+     * List all .bin files located at the selected directory
+     */
+    public void populateFileList()
+    {
+        // Create a list of files
+        mFileList = new ArrayList<>();
+        mFileAdapter = new FileAdapter(this, mFileList);
+        mLvFileList.setAdapter(mFileAdapter);
+
+        if (mDir.exists() && mDir.canRead()) {
+            if(Util.DEBUG) Log.d(TAG, mDir.getPath());
+
+            // Create filter on .bin files
+            FilenameFilter textFilter = new FilenameFilter()
+            {
+                public boolean accept(File dir, String name)
+                {
+                    String lowercaseName = name.toLowerCase();
+                    if (lowercaseName.endsWith(".bin"))
+                    {
+                        return true;
+                    }
+                    else
+                    {
+                        return false;
+                    }
+                }
+            };
+
+            // Create array of all .bin files
+            File[] files = mDir.listFiles(textFilter);
+            if (files == null) {
+                // Show error dialog
+                AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                builder.setTitle("Error")
+                        .setMessage("Could not access internal file storage.")
+                        .setCancelable(false)
+                        .setPositiveButton("OK",
+                                new DialogInterface.OnClickListener() {
+                                    public void onClick(DialogInterface dialog, int id) {
+                                        finish();
+                                    }
+                                })
+                        .show();
+                return;
+            }
+
+            for (File file : files)
+            {
+                if (!file.isDirectory())
+                {
+                    mFileList.add(file.getName());
+                }
+            }
+
+            if (mFileList.size() == 0)
+            {
+                Toast.makeText(this, "No OAD images available", Toast.LENGTH_LONG).show();
+            }
+        }
+        else
+        {
+            Toast.makeText(this, Environment.DIRECTORY_DOWNLOADS + " does not exist or is not readable", Toast.LENGTH_LONG).show();
+        }
+
+        // Select the first item as default
+        if (mFileList.size() > 0)
+        {
+            mFileAdapter.setSelectedPosition(0);
+            mBtnConfirm.setText("Confirm");
+        }
+        else
+        {
+            mBtnConfirm.setText("Cancel");
+        }
+
+    }
+
+}
diff --git a/app/src/main/java/com/example/ti/oadexample/FwUpdateActivity.java b/app/src/main/java/com/example/ti/oadexample/FwUpdateActivity.java
new file mode 100644 (file)
index 0000000..92fc4d0
--- /dev/null
@@ -0,0 +1,576 @@
+package com.example.ti.oadexample;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.Html;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+public class FwUpdateActivity extends AppCompatActivity {
+
+    public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME";
+
+    // Locals
+    private String mDeviceName;
+    private MainActivity mMainActivity;
+    private static final short MIN_BLOCK_DELAY = 10; // make sure block delay is larger than connection interval
+
+    // Tag used for logging
+    private static final String TAG = "FwUpdateActivity";
+
+    // GUI
+    private TextView mUpdateImage;
+    private TextView mConnectionState;
+    private TextView mLog;
+    private Button mBtnProgram;
+    private Button mBtnSelectImage;
+    private ProgressBar mProgressBar;
+    private TextView mProgressInfo;
+    private SeekBar mSbDelay;
+    private TextView mTvDelay;
+
+    // BLE
+    private HashMap<String, BluetoothGattCharacteristic> mGattCharacteristics = new HashMap<>();
+    public BluetoothGattCharacteristic mCharIdentify = null;
+    public BluetoothGattCharacteristic mCharBlock = null;
+    private BluetoothGattCharacteristic mCharConnReq = null;
+    private BluetoothGattCharacteristic mCharTestResult = null;
+    public BluetoothGattCharacteristic mCharImageStatus = null;
+    public BluetoothGattCharacteristic getCharConnReq(){
+        return mCharConnReq;
+    }
+
+    // Programming
+    public int mBlockDelay = 0;
+
+    // Housekeeping
+    boolean mIsConnected = false;
+    private boolean mTestOK = false;
+    private boolean mOadFailedAlertShown = false;
+    boolean mSafeMode = false;
+
+    // Request code for file activity
+    public static final int FILE_ACTIVITY_REQUEST = 0;
+
+    // Class object to handle the OAD process
+    OadProcess mOadProcess = null;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_fw_update);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        // Add back-button to parent activity
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+        // Get name of device that this intent is opened for
+        Intent intent = getIntent();
+        mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME);
+
+        // Set activity title to the device name
+        setTitle(mDeviceName);
+
+        // Get UI elements
+        mProgressInfo = (TextView) findViewById(R.id.tv_info);
+        mConnectionState = (TextView) findViewById(R.id.connection_state);
+        mUpdateImage = (TextView) findViewById(R.id.tv_new_image);
+        mLog = (TextView) findViewById(R.id.tv_log);
+        mBtnProgram = (Button) findViewById(R.id.btn_program);
+        mBtnSelectImage = (Button) findViewById(R.id.btn_selectImage);
+        mProgressBar = (ProgressBar) findViewById(R.id.pb_progress);
+        mSbDelay = (SeekBar) findViewById(R.id.sbDelay);
+        mTvDelay = (TextView) findViewById(R.id.tvDelay);
+
+        // Initialize UI elements
+        mBtnProgram.setEnabled(false);
+        mBtnSelectImage.setEnabled(false);
+        mUpdateImage.setText("");
+        mTvDelay.setText(getDelayText());
+        mBlockDelay = mSbDelay.getProgress() + MIN_BLOCK_DELAY;
+        mSbDelay.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                mBlockDelay = progress + MIN_BLOCK_DELAY; // adding offset because delay must be larger than connection interval
+                mTvDelay.setText(String.valueOf(mBlockDelay) + " ms");
+            }
+
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+        });
+        mLog.setMovementMethod(new ScrollingMovementMethod());
+
+        // Get instance of main activity
+        mMainActivity = (MainActivity) MainActivity.activity;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        // Register a receiver for broadcast updates
+        registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        // Do not receive broadcast updates when paused
+        unregisterReceiver(mGattUpdateReceiver);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mMainActivity = null;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                // Disconnect when home button is pressed
+                Toast.makeText(getApplicationContext(),"Disconnecting", Toast.LENGTH_LONG).show();
+                mMainActivity.disconnect();
+                finish();
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // Check which request we are responding to
+        if (requestCode == FILE_ACTIVITY_REQUEST) {
+            // Make sure the request was successful
+            if (resultCode == RESULT_OK) {
+                String filename = data.getStringExtra(FileActivity.EXTRA_FILENAME);
+                loadFile(filename);
+            }
+        }
+    }
+
+    /**
+     * Method to create the text field for the delay slider
+     * @return text to add next to the slider
+     */
+    public String getDelayText(){
+        return String.valueOf(mSbDelay.getProgress() + MIN_BLOCK_DELAY) + " ms";
+    }
+
+    /**
+     * Function called when the checkbox for selecting safe mode is clicked
+     */
+    public void onSafeMode(View view) {
+
+        // Is the safe mode selected?
+        mSafeMode = ((CheckBox) view).isChecked();
+
+        // Disable block delay slider if safe mode selected
+        mSbDelay.setEnabled(!mSafeMode);
+        if(mSafeMode) {
+            mTvDelay.setText("N/A");
+        }
+        else{
+            mTvDelay.setText(getDelayText());
+        }
+    }
+
+
+    /**
+     * Function called when the user clicks on the select image button
+     */
+    public void onSelectImage(View v) {
+        Intent i = new Intent(this, FileActivity.class);
+        startActivityForResult(i, FILE_ACTIVITY_REQUEST);
+    }
+
+    /**
+     * Function called when user clicks the program/cancel button
+     *
+     * @param v
+     */
+    public void onProgramImage(View v) {
+        if (isProgramming()) {
+            mOadProcess.stopProgramming();
+        } else {
+            // Display warning if not connected
+            if(!mIsConnected)
+            {
+                Toast.makeText(getApplicationContext(),"Device not connect. Please try to re-connect.", Toast.LENGTH_LONG).show();
+                return;
+            }
+            // Start programming
+            mOadFailedAlertShown = false;
+            mOadProcess.startProgramming();
+        }
+    }
+
+    /**
+     * Update GUI based on programming status
+     */
+    public void updateGui(final boolean programming) {
+
+        FwUpdateActivity.this.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (programming) {
+                    // Busy: show cancel button, disable file selector
+                    mBtnProgram.setText(R.string.cancel);
+                    mBtnSelectImage.setEnabled(false);
+                } else {
+                    // Idle: no progress, show program button, enable file selector if connected
+                    mProgressBar.setProgress(0);
+                    mBtnProgram.setText(R.string.start_prog);
+                    mBtnSelectImage.setEnabled(true);
+                }
+
+
+            }
+        });
+
+    }
+
+    /**
+     * Display progress info
+     */
+    public void displayProgressText(String txt)
+    {
+        mProgressInfo.setText(txt);
+    }
+
+    /**
+     * Set progress bar value
+     */
+    public void updateProgressBar(int progress)
+    {
+        mProgressBar.setProgress(progress);
+    }
+
+    /**
+     * Append/Set log text
+     *
+     * @param txt    Text to write to log window
+     * @param append If true, the text will be appended to existing test, otherwise
+     *               it old text will be overridden.
+     */
+    public void log(CharSequence txt, boolean append){
+        if(append){
+            mLog.append(txt);
+        }
+        else{
+            mLog.setText(txt);
+        }
+    }
+
+    /**
+     *  Check if programming is in progress
+     */
+    private boolean isProgramming() {
+        if ((mOadProcess != null) && mOadProcess.isProgramming()){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Function called when the user has chosen a file
+     */
+    private boolean loadFile(String filepath) {
+        int readLen = 0;
+
+        // Check self test result
+        if (mTestOK != true) {
+            AlertDialog.Builder testFailedAlertDialog = new AlertDialog.Builder(this);
+            testFailedAlertDialog.setTitle("Error");
+            testFailedAlertDialog.setMessage("External FLASH self test failed, cannot do OAD on this device, If the device is connected to a debugger remove debugger, remove and insert battery.");
+            testFailedAlertDialog.setNegativeButton("OK", null);
+            (testFailedAlertDialog.create()).show();
+            return false;
+        }
+
+        // Create OAD process object
+        if(mOadProcess == null)
+        {
+            mOadProcess = OadProcess.newInstance(mMainActivity, this);
+        }
+
+        // Read file
+        readLen = mOadProcess.readFile(filepath);
+        if(readLen == -1){
+            mLog.setText("Failed to read image : " + filepath + "\n");
+            return false;
+        }
+
+        // Configure connection parameters
+        if ( Build.VERSION.SDK_INT >= 21)
+        {
+            if (Util.DEBUG) Log.d(TAG,"Requested connection priority HIGH, result : " + mMainActivity.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH));
+        }
+        mOadProcess.setConnectionParameters();
+
+        // Update GUI elements
+        mUpdateImage.setText(filepath);
+        mBtnProgram.setEnabled(true);
+        updateGui(isProgramming());
+
+        // Log
+        mLog.setText("Image to program : " + filepath + "\n");
+        mLog.append("File size : " + readLen + " bytes (" + (readLen / 16) + ") blocks\n");
+        mLog.append("Ready to program device!\n");
+
+        return true;
+    }
+
+    /**
+     * Update the connection status field
+     */
+    private void updateConnectionState(final int resourceId) {
+        FwUpdateActivity.this.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mConnectionState.setText(resourceId);
+
+                // Enable image selection when connected
+                if(mIsConnected){
+                    mBtnSelectImage.setEnabled(true);
+                }
+            }
+        });
+    }
+
+
+    private void updateFlashSelfTestState(final byte[] value) {
+        FwUpdateActivity.this.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if ((value[0] & 0xFF) == 0x01) {
+                    mLog.append(Html.fromHtml(String.format("<font color=#00CC00>FLASH Self test passed !</font>")));
+                    mTestOK = true;
+                }
+                else {
+                    mLog.append(Html.fromHtml(String.format("<font color=#CC0000>FLASH Self test failed !</font>")));
+                    mTestOK = false;
+                }
+            }
+        });
+    }
+
+    /**
+     * Create an intent filter for actions broadcast
+     * by MainActivity
+     *
+     * @return The created IntentFilter object
+     */
+    private static IntentFilter makeGattUpdateIntentFilter() {
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(MainActivity.ACTION_GATT_CONNECTED);
+        intentFilter.addAction(MainActivity.ACTION_GATT_DISCONNECTED);
+        intentFilter.addAction(MainActivity.ACTION_GATT_SERVICES_DISCOVERED);
+        intentFilter.addAction(MainActivity.ACTION_DATA_NOTIFY);
+        intentFilter.addAction(MainActivity.ACTION_DATA_READ);
+        return intentFilter;
+    }
+
+    /**
+     * Iterate through the supported GATT Services/Characteristics,
+     * and initialize UI elements displaying them.
+     */
+    private void initializeGattServiceUIElements(final Context context, List<BluetoothGattService> gattServices) {
+
+        // Loop through services
+        boolean serviceFound = false;
+        for (BluetoothGattService s : gattServices) {
+            if(TIOADProfile.isOadService(s))
+            {
+                // OAD service found
+                if (Util.INFO) Log.i(TAG,"Found TI OAD Service");
+                serviceFound = true;
+
+                // Save characteristics
+                mCharIdentify = s.getCharacteristic(UUID.fromString(TIOADProfile.OAD_IMAGE_NOTIFY_CHAR));
+                mCharBlock = s.getCharacteristic(UUID.fromString(TIOADProfile.OAD_BLOCK_REQUEST_CHAR));
+                mCharBlock.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
+                mCharImageStatus = s.getCharacteristic(UUID.fromString(TIOADProfile.OAD_IMAGE_STATUS_CHAR));
+            }
+            else if (TIOADProfile.isConnectionControlService(s)) {
+                mCharConnReq = s.getCharacteristic(UUID.fromString(TIOADProfile.REQUEST_CONNECTION_PARAMETERS_CHAR));
+            }
+            else if(TIOADProfile.isTestService(s)){
+                // Test service found, save characteristics
+                mCharTestResult = s.getCharacteristic(UUID.fromString(TIOADProfile.TEST_DATA_CHAR));
+
+                // Read test result to check if external flash is ok
+                mMainActivity.readCharacteristic(mCharTestResult);
+            }
+        }
+
+        // Check if self test result is present
+        if (mCharTestResult == null) {
+            // Test service is missing, so we cannot check, print a warning in the text field.
+            mLog.append(Html.fromHtml(String.format("<font color=#CC0000>No test service on current device, cannot check if external flash is working !</font>")));
+            mTestOK = true;
+        }
+
+        // Verify that OAD service was found
+        if(!serviceFound)
+        {
+            FwUpdateActivity.this.runOnUiThread(new Runnable() {
+                @Override
+                public void run()
+                {
+                    // Servie is missing, display an error
+                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
+                    builder.setTitle("Error!");
+                    builder.setMessage("The expected OAD service was not discovered for current device. " +
+                            "The device will be disconnected.");
+                    builder.setPositiveButton("OK", new DialogInterface.OnClickListener()
+                    {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which)
+                        {
+                            // Disconnect and return from activity
+                            mMainActivity.disconnect();
+                            finish();
+                        }
+                    });
+                    AlertDialog a = builder.create();
+                    a.show();
+                }
+            });
+            return;
+        }
+    }
+
+    /**
+     * Handles various events fired by MainActivity
+     * ACTION_GATT_CONNECTED: connected to a GATT server.
+     * ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
+     * ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
+     * ACTION_DATA_NOTIFY: characteristic notification received.
+     * ACTION_DATA_READ: characteristic have been read.
+     */
+    private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            byte [] value;
+            String uuidStr;
+            switch(action){
+                case MainActivity.ACTION_GATT_CONNECTED:
+                    mIsConnected = true;
+                    updateConnectionState(R.string.connected);
+                    break;
+                case MainActivity.ACTION_GATT_DISCONNECTED:
+                    mIsConnected = false;
+                    updateConnectionState(R.string.disconnected);
+                    break;
+                case MainActivity.ACTION_GATT_SERVICES_DISCOVERED:
+                    initializeGattServiceUIElements(context, mMainActivity.getSupportedGattServices());
+                    break;
+                case MainActivity.ACTION_DATA_NOTIFY:
+                    value = intent.getByteArrayExtra(MainActivity.EXTRA_DATA);
+                    uuidStr = intent.getStringExtra(MainActivity.EXTRA_UUID);
+                    if (uuidStr.equals(mCharIdentify.getUuid().toString())) {
+                        // Image verification failed when this notification is received. Create message.
+                        if (Util.ERROR) Log.e(TAG, "Image verification failed");
+                        AlertDialog.Builder alertDialog = new AlertDialog.Builder(FwUpdateActivity.this);
+                        alertDialog.setTitle("Error");
+                        alertDialog.setMessage("Image verification failed, cannot do OAD with this image.");
+                        alertDialog.setNegativeButton("OK", null);
+                        (alertDialog.create()).show();
+
+                        // Disable program button
+                        if (isProgramming()) {
+                            mOadProcess.stopProgramming();
+                        }
+
+                    }
+                    else if (uuidStr.equals(mCharBlock.getUuid().toString())) {
+                        // Block request received
+                        String block = String.format("%02x%02x",value[1],value[0]);
+                        if (Util.DEBUG) Log.d(TAG, "Received block req: " + block);
+                        if(mSafeMode) {
+                            mOadProcess.programBlock();
+                        }
+                    }
+                    else if(uuidStr.equals(mCharImageStatus.getUuid().toString())){
+                        if((value[0] != 0) && !mOadFailedAlertShown){
+                            // Oad error status received. Create dialog
+                            String msg = "";
+                            switch(value[0]){
+                                case 1:
+                                    msg = "The downloaded image’s CRC doesn’t match the one expected from the metadata.";
+                                    break;
+                                case 2:
+                                    msg = "The external flash cannot be opened.";
+                                    break;
+                                case 3:
+                                    msg = "A buffer overflow has occurred. Please try OAD in safe mode or with a longer " +
+                                            "block delay.";
+                                    break;
+                                default:
+                                    break;
+                            }
+                            mOadFailedAlertShown = true;
+                            AlertDialog.Builder alertDialog = new AlertDialog.Builder(FwUpdateActivity.this);
+                            alertDialog.setTitle("Error");
+                            alertDialog.setMessage(String.format("OAD programming failed with status %02x: %s",value[0], msg));
+                            alertDialog.setNegativeButton("OK", null);
+                            (alertDialog.create()).show();
+                            // Stop ongoing programming
+                            if (isProgramming()) {
+                                mOadProcess.stopProgramming();
+                            }
+                        }
+                    }
+                    break;
+                case MainActivity.ACTION_DATA_READ:
+                    value = intent.getByteArrayExtra(MainActivity.EXTRA_DATA);
+                    uuidStr = intent.getStringExtra(MainActivity.EXTRA_UUID);
+                    if (uuidStr.toString().compareTo(TIOADProfile.TEST_DATA_CHAR) == 0) {
+                        // Test result data received, check data
+                        if (value.length > 0) {
+                            if (Util.DEBUG) Log.d(TAG, "Read from " + uuidStr + " data =" + value[0]);
+                            updateFlashSelfTestState(value);
+                        }
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+}
diff --git a/app/src/main/java/com/example/ti/oadexample/MainActivity.java b/app/src/main/java/com/example/ti/oadexample/MainActivity.java
new file mode 100644 (file)
index 0000000..399291c
--- /dev/null
@@ -0,0 +1,690 @@
+package com.example.ti.oadexample;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+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.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.UUID;
+
+public class MainActivity extends AppCompatActivity {
+
+    // Locals
+    private boolean mScanning = false;
+    private BluetoothAdapter mBluetoothAdapter;
+    private BluetoothLeScanner mLEScanner;
+    private ScanSettings mScanSettings;
+    private BluetoothGatt mBluetoothGatt;
+    private ArrayList<ScanFilter> mScanFilters = new ArrayList<>();
+    private Map<BluetoothDevice, Integer> mBtDevices = new HashMap<>();
+    private TableLayout mTableDevices;
+
+    // Bluetooth SIG identifiers
+    public static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+    // Main Activity Object
+    public static Activity activity = null;
+
+    // Tag used for logging
+    private static final String TAG = "MainActivity";
+
+    // Request codes
+    private final static int MY_PERMISSIONS_REQUEST_ENABLE_BT = 1;
+    private final static int MY_PERMISSIONS_REQUEST_MULTIPLE= 2;
+
+    // Intent actions
+    public final static String ACTION_GATT_CONNECTED =
+            "com.example.ti.oadexample.ACTION_GATT_CONNECTED";
+    public final static String ACTION_GATT_DISCONNECTED =
+            "com.example.ti.oadexample.ACTION_GATT_DISCONNECTED";
+    public final static String ACTION_GATT_SERVICES_DISCOVERED =
+            "com.example.ti.oadexample.ACTION_GATT_SERVICES_DISCOVERED";
+    public final static String ACTION_DATA_NOTIFY = "com.example.ti.oadexample.ACTION_DATA_NOTIFY";
+    public final static String ACTION_DATA_READ = "com.example.ti.oadexample.ACTION_DATA_READ";
+    public final static String ACTION_DATA_WRITE = "com.example.ti.oadexample.ACTION_DATA_WRITE";
+
+    // Intent extras
+    public final static String EXTRA_DATA = "com.example.ti.odaexample.EXTRA_DATA";
+    public final static String EXTRA_UUID = "com.example.ti.odaexample.EXTRA_UUID";
+
+    // Que system for descriptor and characteristic actions;
+    private Queue<BleRequest> characteristicQueue = new LinkedList<>();
+    public enum BleRequestOperation {
+        write,
+        read,
+        enableNotification,
+    }
+    public class BleRequest {
+        public int id;
+        public BluetoothGattDescriptor descriptor;
+        public BluetoothGattCharacteristic characteristic;
+        public BleRequestOperation operation;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        activity = this;
+
+        // Get UI elements
+        mTableDevices = (TableLayout) findViewById(R.id.devicesFound);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        // Initializes Bluetooth adapter.
+        final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+        mBluetoothAdapter = bluetoothManager.getAdapter();
+
+        //  For Android M: Check permissions
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+        {
+            int storageAccess = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
+            int locationAccess = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION);
+
+            // App needs location permission for BLE scanning, and external storage access for
+            // loading OAD images
+            if((locationAccess != PackageManager.PERMISSION_GRANTED) ||
+                    (storageAccess != PackageManager.PERMISSION_GRANTED)){
+                requestPermissions(new String[]{
+                                Manifest.permission.ACCESS_COARSE_LOCATION,
+                                Manifest.permission.READ_EXTERNAL_STORAGE},
+                                MY_PERMISSIONS_REQUEST_MULTIPLE);
+
+            }
+        }
+
+        // Configure default scan filter
+        ScanFilter filter = new ScanFilter.Builder().build();
+        mScanFilters.add(filter);
+
+        // Configure default scan settings
+        mScanSettings = new ScanSettings.Builder().build();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
+            // Bluetooth is disabled or not available. Display
+            // a dialog requesting user permission to enable Bluetooth.
+            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+            startActivityForResult(enableBtIntent, MY_PERMISSIONS_REQUEST_ENABLE_BT);
+        } else if (!mScanning) {
+            // Start scanning
+            mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
+            scanLeDevice(true);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled() && mScanning) {
+            // Stop scanning
+            scanLeDevice(false);
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode,
+                                           String permissions[], int[] grantResults) {
+        // Callback have been received from a permission request
+        switch (requestCode) {
+            case MY_PERMISSIONS_REQUEST_MULTIPLE:
+                if((permissions[0] == Manifest.permission.ACCESS_COARSE_LOCATION )
+                    && (grantResults[0] != PackageManager.PERMISSION_GRANTED)) {
+
+                    // Access location was not granted. Display a warning.
+                    final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                    builder.setTitle("Functionality limited");
+                    builder.setMessage("Since location access has not been granted, this app will not display any bluetooth scan results.");
+                    builder.setPositiveButton(android.R.string.ok, null);
+                    builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
+                        @Override
+                        public void onDismiss(DialogInterface dialog) {
+                        }
+                    });
+                    builder.show();
+                }
+                if((permissions.length > 1) && (permissions[1] == Manifest.permission.READ_EXTERNAL_STORAGE )
+                        && (grantResults[1] != PackageManager.PERMISSION_GRANTED)) {
+
+                    // External storage access was not granted. Display a warning.
+                    final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                    builder.setTitle("Functionality limited");
+                    builder.setMessage("The app won't be able to load an OAD image, since external storage access not has been granted.");
+                    builder.setPositiveButton(android.R.string.ok, null);
+                    builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
+                        @Override
+                        public void onDismiss(DialogInterface dialog) {
+                        }
+                    });
+                    builder.show();
+                }
+                break;
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == MY_PERMISSIONS_REQUEST_ENABLE_BT) {
+            if (resultCode == Activity.RESULT_CANCELED) {
+                // Bluetooth was not enabled, end activity
+                finish();
+                return;
+            }
+        }
+    }
+
+    /**
+     * Connect to a bluetooth device. The connection result is reported asynchronously through the
+     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
+     * callback.
+     *
+     * @param btDevice instance of device to connect to.
+     */
+    public void connectToDevice(BluetoothDevice btDevice) {
+
+        if (mBluetoothGatt != null) {
+            mBluetoothGatt.close();
+            mBluetoothGatt = null;
+        }
+
+        mBluetoothGatt = btDevice.connectGatt(this, false, mGattCallback);
+
+        // Stop scanning
+        if (mScanning) {
+            scanLeDevice(false);
+        }
+    }
+
+    /**
+     * Disconnect from a device. The disconnection result
+     * is reported asynchronously through the
+     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
+     * callback.
+     */
+    public void disconnect() {
+        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+            if (Util.WARN) Log.w(TAG, "Bluetooth not initialized");
+            return;
+        }
+        mBluetoothGatt.disconnect();
+    }
+
+    /**
+     * Retrieve a list of supported GATT services on the connected device. This should be
+     * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
+     *
+     * @return A {@code List} of supported services.
+     */
+    public List<BluetoothGattService> getSupportedGattServices() {
+        if (mBluetoothGatt == null){
+            return null;
+        }
+
+        return mBluetoothGatt.getServices();
+    }
+
+    /**
+     * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
+     * asynchronously through the
+     * {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
+     * callback.
+     *
+     * @param characteristic The characteristic to read from.
+     */
+    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
+        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+            if (Util.WARN) Log.w(TAG, "Bluetooth not initialized");
+            return;
+        }
+
+        // Queue the characteristic to read
+        BleRequest req = new BleRequest();
+        req.characteristic = characteristic;
+        req.operation = BleRequestOperation.read;
+        characteristicQueue.add(req);
+
+        // If there is only 1 item in the queue, then read it. If more than 1, it is handled
+        // asynchronously in the callback
+        if((characteristicQueue.size() == 1)) {
+            mBluetoothGatt.readCharacteristic(characteristic);
+        }
+    }
+
+    /**
+     * Request a write on a given {@code BluetoothGattCharacteristic}. The write result is reported
+     * asynchronously through the
+     * {@code BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
+     * callback.
+     *
+     * @param characteristic The characteristic to write to.
+     */
+    public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+            if (Util.WARN) Log.w(TAG, "BluetoothAdapter not initialized");
+            return;
+        }
+        // Queue the characteristic to write
+        BleRequest req = new BleRequest();
+        req.characteristic = characteristic;
+        req.operation = BleRequestOperation.write;
+        characteristicQueue.add(req);
+
+        // If there is only 1 item in the queue, then write it. If more than 1, it is handled
+        // asynchronously in the callback
+        if((characteristicQueue.size() == 1)) {
+            mBluetoothGatt.writeCharacteristic(characteristic);
+        }
+    }
+
+    /**
+     * Request a write on a given {@code BluetoothGattCharacteristic} immediately. The write result is reported
+     * asynchronously through the
+     * {@code BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
+     * callback.
+     *
+     * @param characteristic The characteristic to write to.
+     */
+    public void writeCharacteristicNoResponse(BluetoothGattCharacteristic characteristic) {
+        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+            if (Util.WARN) Log.w(TAG, "BluetoothAdapter not initialized");
+            return;
+        }
+
+        mBluetoothGatt.writeCharacteristic(characteristic);
+    }
+
+    /**
+     * Scan for bluetooth devices
+     *
+     * @param enable Set to true to start scanning or to false to stop scanning
+     */
+    private void scanLeDevice(final boolean enable) {
+
+        if(mLEScanner == null) {
+            if (Util.WARN) Log.w(TAG, "Could not get LEScanner object");
+            return;
+        }
+
+        if (enable) {
+            // Clear list of scanned devices
+            mBtDevices.clear();
+            mScanning = true;
+            mLEScanner.startScan(mScanFilters, mScanSettings, mScanCallback);
+        } else {
+            mScanning = false;
+            mLEScanner.stopScan(mScanCallback);
+        }
+    }
+
+    /**
+     * Enable or disable notification on a given characteristic.
+     *
+     * @param characteristic Characteristic to act on.
+     * @param enable If true, enable notification. Otherwise, disable it.
+     */
+    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+                                              boolean enable) {
+        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+            if (Util.WARN) Log.w(TAG, "Bluetooth not initialized");
+            return;
+        }
+        // Enable/disable notification
+        boolean status = mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
+        if(status == false){
+            if (Util.WARN) Log.w(TAG, "Set notification failed");
+            return;
+        }
+
+        // Write descriptor for notification
+        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG);
+        if(descriptor != null)
+        {
+            descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : new byte[]{0x00, 0x00});
+            writeGattDescriptor(descriptor);
+        }
+    }
+
+    /**
+     * Set the connection priority.
+     *
+     * @param connectionPriority
+     * @return bool whether the call was successful
+     */
+    public boolean requestConnectionPriority(int connectionPriority) {
+        return this.mBluetoothGatt.requestConnectionPriority(connectionPriority);
+    }
+
+    /**
+     * Bluetooth gatt callback function
+     */
+    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+
+        @Override
+        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+            if(Util.INFO) Log.i(TAG, "onConnectionStateChange. Status: " + status);
+            String intentAction;
+
+            switch (newState) {
+                case BluetoothProfile.STATE_CONNECTED:
+                    intentAction = ACTION_GATT_CONNECTED;
+                    broadcastUpdate(intentAction);
+                    if(Util.INFO) Log.i(TAG, "STATE_CONNECTED");
+                    gatt.discoverServices();
+                    break;
+                case BluetoothProfile.STATE_DISCONNECTED:
+                    if(Util.INFO) Log.i(TAG, "STATE_DISCONNECTED");
+                    intentAction = ACTION_GATT_DISCONNECTED;
+                    broadcastUpdate(intentAction);
+                    // Close connection completely after disconnect, to be able
+                    // to start clean.
+                    if (mBluetoothGatt != null) {
+                        mBluetoothGatt.close();
+                        mBluetoothGatt = null;
+                    }
+                    break;
+                default:
+                    if (Util.ERROR) Log.e(TAG, "STATE_OTHER");
+            }
+        }
+
+        @Override
+        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            Log.i(TAG, "onServicesDiscovered: " + status);
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                // Broadcast that services has successfully been discovered
+                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
+            } else {
+                if (Util.WARN) Log.w(TAG, "onServicesDiscovered received with error: " + status);
+            }
+        }
+
+        @Override
+        public void onCharacteristicChanged(BluetoothGatt gatt,
+                                            BluetoothGattCharacteristic characteristic) {
+            broadcastUpdate(ACTION_DATA_NOTIFY, characteristic);
+        }
+
+        @Override
+        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
+                                         int status) {
+
+            // Read action has finished, remove from queue
+            characteristicQueue.remove();
+
+            // Broadcast the result
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                broadcastUpdate(ACTION_DATA_READ, characteristic);
+            }
+            else{
+                if (Util.ERROR) Log.e(TAG, "onCharacteristicRead error: " + status);
+            }
+
+            // Handle the next element from the queue
+            if(characteristicQueue.size() > 0){
+                BleRequest req = characteristicQueue.element();
+                switch(req.operation)
+                {
+                    case write:
+                        mBluetoothGatt.writeCharacteristic(req.characteristic);
+                        break;
+                    case read:
+                        mBluetoothGatt.readCharacteristic(req.characteristic);
+                        break;
+                    case enableNotification:
+                        mBluetoothGatt.writeDescriptor(req.descriptor);
+                        break;
+                }
+            }
+        }
+
+        @Override
+        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+            if (status != BluetoothGatt.GATT_SUCCESS) {
+                if (Util.ERROR) Log.e(TAG, "Callback: Error writing GATT Descriptor: "+ status);
+            }
+
+            // Write finished, remove from queue
+            characteristicQueue.remove();
+
+            // Continue handling items if there is more in the queue
+            if(characteristicQueue.size() > 0){
+                BleRequest req = characteristicQueue.element();
+                switch(req.operation)
+                {
+                    case write:
+                        mBluetoothGatt.writeCharacteristic(req.characteristic);
+                        break;
+                    case read:
+                        mBluetoothGatt.readCharacteristic(req.characteristic);
+                        break;
+                    case enableNotification:
+                        mBluetoothGatt.writeDescriptor(req.descriptor);
+                        break;
+                }
+            }
+
+        };
+
+        @Override
+        public void onCharacteristicWrite(BluetoothGatt gatt,
+                                          BluetoothGattCharacteristic characteristic, int status) {
+
+            if (Util.INFO) Log.i(TAG, "onCharacteristicWrite: "+ status);
+
+            // Broadcast the result
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                broadcastUpdate(ACTION_DATA_WRITE, characteristic);
+            }
+            else {
+                if (Util.WARN) Log.w(TAG, "Write failed");
+            }
+
+            // Handle the queue if used
+            if((characteristicQueue.size() > 0)) {
+
+                // Write action has finished, remove from queue
+                characteristicQueue.remove();
+
+                // Handle the next element from the queue
+                if (characteristicQueue.size() > 0) {
+                    BleRequest req = characteristicQueue.element();
+                    switch (req.operation) {
+                        case write:
+                            mBluetoothGatt.writeCharacteristic(req.characteristic);
+                            break;
+                        case read:
+                            mBluetoothGatt.readCharacteristic(req.characteristic);
+                            break;
+                        case enableNotification:
+                            mBluetoothGatt.writeDescriptor(req.descriptor);
+                            break;
+                    }
+                }
+            }
+        }
+    };
+
+    /**
+     * Device scan callback
+     */
+    private ScanCallback mScanCallback = new ScanCallback() {
+
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+
+            final BluetoothDevice btDevice = result.getDevice();
+            if (btDevice == null){
+                if (Util.ERROR) Log.e("ScanCallback", "Could not get bluetooth device");
+                return;
+            }
+
+            // Check if device already added to list of scanned devices
+            String macAddress = btDevice.getAddress();
+            for(BluetoothDevice dev : mBtDevices.keySet())
+            {
+                // Device already added, do nothing
+                if(dev.getAddress().equals(macAddress) ){
+                    return;
+                }
+            }
+
+            // Add device to list of scanned devices
+            mBtDevices.put(btDevice, result.getRssi());
+
+            // Update the device table with the new device
+            updateDeviceTable();
+        }
+    };
+
+    /**
+     * Update the table view displaying all scanned devices.
+     * This function will clean the current table view and re-add all items that has been scanned
+     */
+    private void updateDeviceTable() {
+
+        // Clean current table view
+        mTableDevices.removeAllViews();
+
+        for(final BluetoothDevice savedDevice : mBtDevices.keySet()) {
+
+            // Get RSSI of this device
+            int rssi = mBtDevices.get(savedDevice);
+
+            // Create a new row
+            final TableRow tr = new TableRow(MainActivity.this);
+            tr.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT));
+
+            // Add Text view for rssi
+            TextView tvRssi = new TextView(MainActivity.this);
+            tvRssi.setText(Integer.toString(rssi));
+            TableRow.LayoutParams params = new TableRow.LayoutParams(0);
+            params.setMargins(20,0,20,0);
+            tvRssi.setLayoutParams(params);
+
+            // Add Text view for device, displaying name and address
+            TextView tvDevice = new TextView(MainActivity.this);
+            String deviceName = savedDevice.getName();
+            if(deviceName == null){
+                deviceName ="";
+            }
+            tvDevice.setText(deviceName + "\r\n" + savedDevice.getAddress());
+            tvDevice.setLayoutParams(new TableRow.LayoutParams(1));
+
+            // Add a connect button to the right
+            Button b = new Button(MainActivity.this);
+            b.setText(R.string.button_connect);
+            b.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
+
+            // Create action when clicking the connect button
+            b.setOnClickListener(new View.OnClickListener() {
+
+                public void onClick(View v) {
+
+                    // Create the activity for the selected device
+                    final Intent intent = new Intent(MainActivity.this, FwUpdateActivity.class);
+                    intent.putExtra(FwUpdateActivity.EXTRAS_DEVICE_NAME, savedDevice.getName());
+
+                    // Connect to device
+                    connectToDevice(savedDevice);
+
+                    // start activity
+                    startActivity(intent);
+                }
+            });
+
+            // Add items to the row
+            tr.addView(tvRssi);
+            tr.addView(tvDevice);
+            tr.addView(b);
+            tr.setGravity(Gravity.CENTER);
+
+            // Add row to the table layout
+            MainActivity.this.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mTableDevices.addView(tr);
+                }
+            });
+        }
+    }
+
+    /**
+     * Broadcast an update on the specified action
+     */
+    private void broadcastUpdate(final String action) {
+        final Intent intent = new Intent(action);
+        sendBroadcast(intent);
+    }
+
+    /**
+     * Broadcast an update on the specified action
+     */
+    private void broadcastUpdate(final String action,
+                                 final BluetoothGattCharacteristic characteristic) {
+
+        final Intent intent = new Intent(action);
+        intent.putExtra(EXTRA_UUID, characteristic.getUuid().toString());
+        intent.putExtra(EXTRA_DATA, characteristic.getValue());
+        sendBroadcast(intent);
+    }
+
+    /**
+     * Write gatt descriptor if queue is ready.
+     */
+    private void writeGattDescriptor(BluetoothGattDescriptor d){
+        // Add descriptor to the write queue
+        BleRequest req = new BleRequest();
+        req.descriptor = d;
+        req.operation = BleRequestOperation.enableNotification;
+        characteristicQueue.add(req);
+        // If there is only 1 item in the queue, then write it. If more than 1, it will be handled
+        // in the onDescriptorWrite callback
+        if(characteristicQueue.size() == 1){
+            mBluetoothGatt.writeDescriptor(d);
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/com/example/ti/oadexample/OadProcess.java b/app/src/main/java/com/example/ti/oadexample/OadProcess.java
new file mode 100644 (file)
index 0000000..0d9963c
--- /dev/null
@@ -0,0 +1,474 @@
+package com.example.ti.oadexample;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.DialogInterface;
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Class containing the OAD handling
+ */
+public class OadProcess
+{
+
+    private FwUpdateActivity mFwUpdateActivity = null;
+    private MainActivity mMainActivity = null;
+
+    // Tag used for logging
+    private static final String TAG = "OadProcess";
+
+    // Bluetooth
+    private BluetoothGattCharacteristic mCharIdentify = null;
+    private BluetoothGattCharacteristic mCharBlock = null;
+
+    // Programming
+    private static final short OAD_CONN_INTERVAL = 12; // units of 1.25ms = 15ms
+    private static final short OAD_SUPERVISION_TIMEOUT = 50; // units of 10 ms =  500 milliseconds
+    private static final int OAD_BLOCK_SIZE = 16;
+    private static final int OAD_BUFFER_SIZE = 2 + OAD_BLOCK_SIZE;
+    private static final int HAL_FLASH_WORD_SIZE = 4;
+    private static final long TIMER_INTERVAL = 1000;
+    public static final int FILE_BUFFER_SIZE = 0x40000;
+    private byte[] mFileBuffer = new byte[FILE_BUFFER_SIZE];
+    private ImageHeader mFileImgHdr;
+    private final byte[] mOadBuffer = new byte[OAD_BUFFER_SIZE];
+    private ProgramInfo mProgramInfo = new ProgramInfo();
+    private Timer mTimer = null;
+    private TimerTask mTimerTask = null;
+    private Handler mFastModeHandler;
+
+    // Housekeeping
+    private boolean mProgramming = false;
+    boolean mImageHasHeader = false;
+
+
+    /**
+     * Constructor
+     */
+    public static OadProcess newInstance( MainActivity mainActivity, FwUpdateActivity fwUpdateActivity) {
+        OadProcess oad = new OadProcess();
+        oad.mCharBlock = fwUpdateActivity.mCharBlock;
+        oad.mCharIdentify = fwUpdateActivity.mCharIdentify;
+        oad.mFwUpdateActivity = fwUpdateActivity;
+        oad.mMainActivity = mainActivity;
+        return oad;
+    }
+
+    /**
+     *  Inner class for fw image header info
+     */
+    private class ImageHeader {
+        short crc0;
+        short crc1;
+        short version;
+        int length;
+        byte[] uid = new byte[4];
+        short address;
+        byte imgType;
+        byte state;
+
+        /**
+         * Constructor
+         * @param buffer buffer with image to program
+         * @param fileLen length of image to program
+         */
+        ImageHeader(byte[] buffer, int fileLen) {
+
+            // Check if image header exists in file
+            if(fileLen > 15){
+                int i = (mFileBuffer[11] << 24) | (mFileBuffer[10]<< 16) |
+                        (mFileBuffer[9] << 8) | mFileBuffer[8];
+                if (i== 0x45454545){
+                    // Header exist, read it
+                    mImageHasHeader = true;
+                    this.length = Util.buildUint16(mFileBuffer[7], mFileBuffer[6]);
+                    this.version = Util.buildUint16(mFileBuffer[5], mFileBuffer[4]);
+                    this.uid[0] = this.uid[1] = this.uid[2] = this.uid[3] = 'E';
+                    this.address = Util.buildUint16(mFileBuffer[13], mFileBuffer[12]);
+                    this.imgType = mFileBuffer[14];
+                    this.state = mFileBuffer[15];
+                    this.crc0 = Util.buildUint16(mFileBuffer[1], mFileBuffer[0]);
+                    crc1 = Util.buildUint16(mFileBuffer[3], mFileBuffer[2]);
+                    if (Util.DEBUG) {
+                        Log.d(TAG, "Read Header");
+                        Log.d(TAG, "ImgHdr.length = " + this.length);
+                        Log.d(TAG, "ImgHdr.version = " + this.version);
+                        Log.d(TAG, String.format("ImgHdr.uid = %02x%02x%02x%02x", this.uid[0], this.uid[1], this.uid[2], this.uid[3]));
+                        Log.d(TAG, "ImgHdr.address = " + this.address);
+                        Log.d(TAG, "ImgHdr.imgType = " + this.imgType);
+                        Log.d(TAG, String.format("ImgHdr.crc0 = %04x", this.crc0));
+                    }
+
+                    return;
+                }
+            }
+
+            // Header not found in file, create one
+            this.length = (fileLen / 4);
+            this.version = 0;
+            this.uid[0] = this.uid[1] = this.uid[2] = this.uid[3] = 'E';
+            this.address = 0;
+            this.imgType = 1; //EFL_OAD_IMG_TYPE_APP
+            this.crc0 = calcImageCRC((int)0,buffer);
+            crc1 = (short)0xFFFF;
+            this.state = (byte)0xFF;
+            if (Util.DEBUG) {
+                Log.d(TAG, "Generated Header");
+                Log.d(TAG, "ImgHdr.length = " + this.length);
+                Log.d(TAG, "ImgHdr.version = " + this.version);
+                Log.d(TAG, String.format("ImgHdr.uid = %02x%02x%02x%02x", this.uid[0], this.uid[1], this.uid[2], this.uid[3]));
+                Log.d(TAG, "ImgHdr.address = " + this.address);
+                Log.d(TAG, "ImgHdr.imgType = " + this.imgType);
+                Log.d(TAG, String.format("ImgHdr.crc0 = %04x", this.crc0));
+            }
+        }
+
+        /**
+         * Function returning a byte array with image header
+         */
+        byte[] getRequest() {
+            byte[] tmp = new byte[16];
+            tmp[0] = Util.loUint16(this.crc0);
+            tmp[1] = Util.hiUint16(this.crc0);
+            tmp[2] = Util.loUint16(this.crc1);
+            tmp[3] = Util.hiUint16(this.crc1);
+            tmp[4] = Util.loUint16(this.version);
+            tmp[5] = Util.hiUint16(this.version);
+            tmp[6] = Util.loUint16((short)this.length);
+            tmp[7] = Util.hiUint16((short)this.length);
+            tmp[8] = tmp[9] = tmp[10] = tmp[11] = this.uid[0];
+            tmp[12] = Util.loUint16(this.address);
+            tmp[13] = Util.hiUint16(this.address);
+            tmp[14] = imgType;
+            tmp[15] = (byte) 0xFF;
+            return tmp;
+        }
+
+        /**
+         * Calculate the CRC of image to program
+         */
+        short calcImageCRC(int page, byte[] buf) {
+            short crc = 0;
+            long addr = page * 0x1000;
+
+            byte pageBeg = (byte)page;
+            byte pageEnd = (byte)(this.length / (0x1000 / 4));
+            int osetEnd = ((this.length - (pageEnd * (0x1000 / 4))) * 4);
+
+            pageEnd += pageBeg;
+
+            while (true) {
+                int oset;
+
+                for (oset = 0; oset < 0x1000; oset++) {
+                    if ((page == pageBeg) && (oset == 0x00)) {
+                        // Skip the CRC and shadow.
+                        // Note: this increments by 3 because oset is incremented by 1 in each pass
+                        // through the loop
+                        oset += 3;
+                    }
+                    else if ((page == pageEnd) && (oset == osetEnd)) {
+                        crc = this.crc16(crc,(byte)0x00);
+                        crc = this.crc16(crc,(byte)0x00);
+
+                        return crc;
+                    }
+                    else {
+                        crc = this.crc16(crc,buf[(int)(addr + oset)]);
+                    }
+                }
+                page += 1;
+                addr = page * 0x1000;
+            }
+        }
+
+        short crc16(short crc, byte val) {
+            final int poly = 0x1021;
+            byte cnt;
+            for (cnt = 0; cnt < 8; cnt++, val <<= 1) {
+                byte msb;
+                if ((crc & 0x8000) == 0x8000) {
+                    msb = 1;
+                }
+                else msb = 0;
+
+                crc <<= 1;
+                if ((val & 0x80) == 0x80) {
+                    crc |= 0x0001;
+                }
+                if (msb == 1) {
+                    crc ^= poly;
+                }
+            }
+
+            return crc;
+        }
+    }
+
+    /**
+     * Inner class for the programming timer
+     */
+    private class ProgramTimerTask extends TimerTask {
+        @Override
+        public void run() {
+            mProgramInfo.iTimeElapsed += TIMER_INTERVAL;
+        }
+    }
+
+    /**
+     * Inner class keeping programming status
+     */
+    private class ProgramInfo {
+        int iBytes = 0; // Number of bytes programmed
+        short iBlocks = 0; // Number of blocks programmed
+        short nBlocks = 0; // Total number of blocks
+        int iTimeElapsed = 0; // Time elapsed in milliseconds
+
+        void reset() {
+            iBytes = 0;
+            iBlocks = 0;
+            iTimeElapsed = 0;
+            nBlocks = (short) (mFileImgHdr.length / (OAD_BLOCK_SIZE / HAL_FLASH_WORD_SIZE));
+        }
+    }
+
+    /**
+     * Display the programming progress
+     */
+    private void displayProgress() {
+        String txt;
+        int byteRate;
+        int sec = mProgramInfo.iTimeElapsed / 1000;
+        if (sec > 0) {
+            byteRate = mProgramInfo.iBytes / sec;
+        } else {
+            return;
+        }
+        float timeEstimate = ((float)(mFileImgHdr.length *4) / (float)mProgramInfo.iBytes) * sec;
+
+        txt = String.format("Time: %d / %d sec", sec, (int)timeEstimate);
+        txt += String.format("    Bytes: %d (%d/sec)", mProgramInfo.iBytes, byteRate);
+        mFwUpdateActivity.displayProgressText(txt);
+    }
+
+    /**
+     *  Check if programming is in action
+     */
+    public boolean isProgramming()
+    {
+        return mProgramming;
+    }
+
+    /**
+     * Called when the user has chosen a file
+     */
+    public int readFile(String filepath) {
+        int readLen = 0;
+
+        // Load binary file
+        try {
+            // Read the file raw into a buffer
+            InputStream stream;
+            File f = new File(filepath);
+            stream = new FileInputStream(f);
+            readLen = stream.read(mFileBuffer, 0, mFileBuffer.length);
+            stream.close();
+        } catch (IOException e) {
+            // Handle exceptions here
+            mFwUpdateActivity.log("File open failed: " + filepath + "\n", false);
+            return -1;
+        }
+
+        // Pad image with 0xFF to align with 16 bytes
+        if((readLen % 16) != 0){
+            Log.d(TAG, "length = " + mFileBuffer.length);
+            while((readLen % 16) != 0){
+                mFileBuffer[readLen] = (byte)0xFF;
+                readLen++;
+            }
+        }
+
+        // Create image header
+        mFileImgHdr = new ImageHeader(mFileBuffer,readLen);
+        if (mImageHasHeader && (mFileImgHdr.state == (byte)0xFE))
+        {
+            // Image header is written in the file, do not include it in the programming
+            System.arraycopy(mFileBuffer, 16,mFileBuffer, 0, mFileBuffer.length - 16);
+            readLen -= 16;
+        }
+
+        return readLen;
+    }
+
+    /**
+    * Program one block of bytes.
+    * In safe mode, this function is called when a notification with the current image info
+    * has been received. In unsafe mode, it is called repeatedly with a delay.
+    */
+    public void programBlock() {
+        if (!mProgramming)
+            return;
+
+        if (mProgramInfo.iBlocks < mProgramInfo.nBlocks)
+        {
+            mProgramming = true;
+
+            // Prepare block
+            mOadBuffer[0] = Util.loUint16(mProgramInfo.iBlocks);
+            mOadBuffer[1] = Util.hiUint16(mProgramInfo.iBlocks);
+            System.arraycopy(mFileBuffer, mProgramInfo.iBytes, mOadBuffer, 2, OAD_BLOCK_SIZE);
+
+            // Send block
+            mCharBlock.setValue(mOadBuffer);
+            mMainActivity.writeCharacteristicNoResponse(mCharBlock);
+            String block = String.format("%02x%02x",mOadBuffer[1],mOadBuffer[0]);
+            if (Util.DEBUG) Log.d(TAG,"Sent block :" + block /*mProgramInfo.iBlocks*/);
+
+            // Update statistics
+            mProgramInfo.iBlocks++;
+            mProgramInfo.iBytes += OAD_BLOCK_SIZE;
+            mFwUpdateActivity.updateProgressBar((mProgramInfo.iBlocks * 100) / mProgramInfo.nBlocks);
+            if (mProgramInfo.iBlocks == mProgramInfo.nBlocks) {
+
+                // Programming has finished
+                AlertDialog.Builder b = new AlertDialog.Builder(mFwUpdateActivity);
+                b.setMessage(R.string.oad_dialog_programming_finished);
+                b.setTitle("Programming finished");
+                b.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, int i) {
+                        mFwUpdateActivity.finish();
+                    }
+                });
+
+                AlertDialog d = b.create();
+                d.show();
+                mProgramming = false;
+                mFwUpdateActivity.log(("Programming finished at block " + (mProgramInfo.iBlocks + 1) + "\n"), true);
+            }
+
+        }
+        else
+        {
+            mProgramming = false;
+        }
+
+        if ((mProgramInfo.iBlocks % 100) == 0) {
+            // Display statistics each 100th block
+            mFwUpdateActivity.runOnUiThread(new Runnable() {
+                public void run() {
+                    displayProgress();
+                }
+            });
+        }
+
+        if (!mProgramming)
+        {
+            mFwUpdateActivity.runOnUiThread(new Runnable() {
+                public void run() {
+                    displayProgress();
+                    stopProgramming();
+                }
+            });
+        }
+    }
+
+    /**
+     * Start programming image
+     */
+    public void startProgramming() {
+
+        // Enable notifications on characteristics
+        mMainActivity.setCharacteristicNotification(mCharIdentify,true);
+        mMainActivity.setCharacteristicNotification(mCharBlock,true);
+        BluetoothGattCharacteristic imageStatusChar = mFwUpdateActivity.mCharImageStatus;
+        mMainActivity.setCharacteristicNotification(imageStatusChar,true);
+
+        // Send image header
+        mCharIdentify.setValue(mFileImgHdr.getRequest());
+        mMainActivity.writeCharacteristic(mCharIdentify);
+
+        // Update GUI
+        mFwUpdateActivity.log("Programming started\n", true);
+        mProgramming = true;
+        mFwUpdateActivity.updateGui(mProgramming);
+
+        // Initialize statistics
+        mProgramInfo.reset();
+        mTimer = new Timer();
+        mTimerTask = new ProgramTimerTask();
+        mTimer.scheduleAtFixedRate(mTimerTask, 0, TIMER_INTERVAL);
+
+        if(!mFwUpdateActivity.mSafeMode){
+            // Fast mode. Start runnable that program blocks with a delay
+            mFastModeHandler = new Handler();
+            mFastModeHandler.postDelayed(r, 150);
+        }
+    }
+
+    /**
+     * Runnable used for fast programming mode. Blocks of bytes
+     * are programmed continuously with a delay
+     */
+    private Runnable r = new Runnable() {
+
+        @Override
+        public void run() {
+            if(mProgramming){
+                // Program block and delay
+                programBlock();
+                mFastModeHandler.postDelayed(this, mFwUpdateActivity.mBlockDelay);
+            }
+            else{
+                // Stop runnable
+                mFastModeHandler.removeCallbacks(this);
+            }
+        }
+    };
+
+    /**
+     * Stop programming of image
+     */
+    public void stopProgramming() {
+        mTimer.cancel();
+        mTimer.purge();
+        mTimerTask.cancel();
+        mTimerTask = null;
+
+        mProgramming = false;
+        mFwUpdateActivity.displayProgressText("");
+        mFwUpdateActivity.updateProgressBar(0);
+        mFwUpdateActivity.updateGui(mProgramming);
+
+        if (mProgramInfo.iBlocks == mProgramInfo.nBlocks) {
+            mFwUpdateActivity.log("Programming complete!\n", false);
+        } else {
+            mFwUpdateActivity.log("Programming cancelled\n", true);
+        }
+
+        // Disable notification on characteristics
+        mMainActivity.setCharacteristicNotification(mCharBlock, false);
+    }
+
+    /**
+     * Function trying to set the BLE connection parameters
+     */
+    public void setConnectionParameters() {
+        // Make sure connection interval is long enough for OAD
+        byte[] value = {Util.loUint16(OAD_CONN_INTERVAL), Util.hiUint16(OAD_CONN_INTERVAL), Util.loUint16(OAD_CONN_INTERVAL),
+                Util.hiUint16(OAD_CONN_INTERVAL), 0, 0, Util.loUint16(OAD_SUPERVISION_TIMEOUT), Util.hiUint16(OAD_SUPERVISION_TIMEOUT) };
+
+        BluetoothGattCharacteristic charConnReq = mFwUpdateActivity.getCharConnReq();
+        charConnReq.setValue(value);
+        mMainActivity.writeCharacteristic(charConnReq);
+    }
+
+}
diff --git a/app/src/main/java/com/example/ti/oadexample/TIOADProfile.java b/app/src/main/java/com/example/ti/oadexample/TIOADProfile.java
new file mode 100644 (file)
index 0000000..490cbf2
--- /dev/null
@@ -0,0 +1,87 @@
+package com.example.ti.oadexample;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import java.util.UUID;
+
+
+public class TIOADProfile
+{
+       // Test service
+       public static final String TEST_SERVICE = "f000aa64-0451-4000-b000-000000000000";
+       public static final String TEST_DATA_CHAR  = "f000aa65-0451-4000-b000-000000000000"; // Test result
+
+       // OAD service
+       public static final String OAD_SERVICE = "f000ffc0-0451-4000-b000-000000000000";
+       public static final String OAD_IMAGE_NOTIFY_CHAR = "f000ffc1-0451-4000-b000-000000000000";
+       public static final String OAD_BLOCK_REQUEST_CHAR = "f000ffc2-0451-4000-b000-000000000000";
+       public static final String OAD_IMAGE_STATUS_CHAR = "f000ffc4-0451-4000-b000-000000000000";
+
+       // Connection control service
+       public static final String CONNECTION_CONTROL_SERVICE = "f000ccc0-0451-4000-b000-000000000000";
+       public static final String REQUEST_CONNECTION_PARAMETERS_CHAR = "f000ccc2-0451-4000-b000-000000000000";
+
+       /**
+        * Verify that the given service is a TIOAD service
+        */
+    public static boolean isOadService(BluetoothGattService service)
+       {
+               if ((service.getUuid().toString().compareTo(OAD_SERVICE)) == 0)
+               {
+                       // Verify the correct characteristics
+                       BluetoothGattCharacteristic c = service.getCharacteristic(UUID.fromString(OAD_IMAGE_NOTIFY_CHAR));
+                       if(c == null)
+                       {
+                               return false;
+                       }
+                       c = service.getCharacteristic(UUID.fromString(OAD_BLOCK_REQUEST_CHAR));
+                       if(c == null)
+                       {
+                               return false;
+                       }
+                       c = service.getCharacteristic(UUID.fromString(OAD_IMAGE_STATUS_CHAR));
+                       if(c == null)
+                       {
+                               return false;
+                       }
+                       return true;
+               }
+               return false;
+       }
+
+       /**
+        * Verify that the given service is the Test service
+        */
+       public static boolean isTestService(BluetoothGattService service) {
+
+               if ((service.getUuid().toString().compareTo(TEST_SERVICE)) == 0)
+               {
+                       // Verify the correct characteristics
+                       BluetoothGattCharacteristic c = service.getCharacteristic(UUID.fromString(TEST_DATA_CHAR));
+                       if(c == null)
+                       {
+                               return false;
+                       }
+                       return true;
+               }
+               return false;
+       }
+
+       /**
+        * Verify that the given service is the Connection Control service
+        */
+       public static boolean isConnectionControlService(BluetoothGattService service) {
+               if ((service.getUuid().toString().compareTo(CONNECTION_CONTROL_SERVICE)) == 0)
+               {
+                       // Verify the correct characteristics
+                       BluetoothGattCharacteristic c = service.getCharacteristic(UUID.fromString(REQUEST_CONNECTION_PARAMETERS_CHAR));
+                       if(c == null)
+                       {
+                               return false;
+                       }
+                       return true;
+               }
+               return false;
+       }
+
+}
diff --git a/app/src/main/java/com/example/ti/oadexample/Util.java b/app/src/main/java/com/example/ti/oadexample/Util.java
new file mode 100644 (file)
index 0000000..4c98936
--- /dev/null
@@ -0,0 +1,35 @@
+package com.example.ti.oadexample;
+
+/**
+ * Utility class
+ */
+public class Util
+{
+    // Choose  loglevel
+    public static int LOGLEVEL = 1;
+    public static boolean ERROR = LOGLEVEL > 0;
+    public static boolean WARN = LOGLEVEL > 1;
+    public static boolean INFO = LOGLEVEL > 2;
+    public static boolean DEBUG = LOGLEVEL > 3;
+
+    /**
+     * Get lower byte of an uint16
+     */
+    public static byte loUint16(short v) {
+        return (byte) (v & 0xFF);
+    }
+
+    /**
+     * Get high byte of an uint16
+     */
+    public static byte hiUint16(short v) {
+        return (byte) (v >> 8);
+    }
+
+    /**
+     * Build a uint16 from two bytes
+     */
+    public static short buildUint16(byte hi, byte lo) {
+        return (short) ((hi << 8) + (lo & 0xff));
+    }
+}
diff --git a/app/src/main/res/drawable/gradient_bg.xml b/app/src/main/res/drawable/gradient_bg.xml
new file mode 100644 (file)
index 0000000..13b67fe
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <gradient
+        android:startColor="#f1f1f2"
+        android:centerColor="#e7e7e8"
+        android:endColor="#cfcfcf"
+        android:angle="270" />
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/gradient_pressed_bg.xml b/app/src/main/res/drawable/gradient_pressed_bg.xml
new file mode 100644 (file)
index 0000000..ed87256
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <gradient
+        android:startColor="#4d5ec1"
+        android:endColor="#7380ce"
+        android:angle="270" />
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/group_box.xml b/app/src/main/res/drawable/group_box.xml
new file mode 100644 (file)
index 0000000..a189052
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#ffffff" />
+    <stroke android:width="4dp" android:color="#ffffff" />
+    <padding android:left="7dp" android:top="7dp"
+             android:right="7dp" android:bottom="7dp" />
+    <corners android:radius="4dp" />
+
+</shape>
diff --git a/app/src/main/res/drawable/list_border.xml b/app/src/main/res/drawable/list_border.xml
new file mode 100644 (file)
index 0000000..69b1519
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+    <stroke android:width="1dp" android:color="#b5b5b5" />
+    <solid android:color="#00000000" />
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/states_selector_list.xml b/app/src/main/res/drawable/states_selector_list.xml
new file mode 100644 (file)
index 0000000..e97864c
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/gradient_bg"/>
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_file.xml b/app/src/main/res/layout/activity_file.xml
new file mode 100644 (file)
index 0000000..ac26db7
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context="com.example.ti.oadexample.FileActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_file" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/activity_fw_update.xml b/app/src/main/res/layout/activity_fw_update.xml
new file mode 100644 (file)
index 0000000..a475464
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context="com.example.ti.oadexample.FwUpdateActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_fw_update" />
+</android.support.design.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644 (file)
index 0000000..5f61dc5
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context="com.example.ti.oadexample.MainActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_main" />
+</android.support.design.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/content_file.xml b/app/src/main/res/layout/content_file.xml
new file mode 100644 (file)
index 0000000..cf10995
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="10dp"
+    tools:context="com.example.ti.oadexample.FileActivity"
+    tools:showIn="@layout/activity_file"
+    android:focusableInTouchMode="true"
+    >
+
+    <TextView android:id="@+id/lbl_directory"
+        android:text="@string/label_directory"
+        android:layout_marginTop="10dp"
+        android:gravity="center_horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"/>
+
+    <EditText
+        android:id="@+id/et_directory"
+        android:text="download"
+        android:textColor="#003"
+        android:gravity="center_horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/lbl_directory"
+        android:singleLine="true"/>
+    <ImageButton android:id="@+id/data_write"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:onClick="onDirChanged"
+        android:background="@null"
+        android:src="@drawable/ic_action_refresh"
+        android:layout_alignTop="@id/et_directory"
+        android:layout_alignBottom="@id/et_directory"
+        android:layout_alignRight="@id/et_directory" />
+
+    <ListView
+        android:id="@+id/lv_file"
+        android:layout_marginTop="10dp"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_above="@+id/btn_confirm"
+        android:layout_below="@id/et_directory"
+        android:choiceMode="singleChoice"
+        android:padding="1dp"
+        android:divider="#b5b5b5"
+        android:dividerHeight="1dp"
+        android:headerDividersEnabled="true"
+        android:background="@drawable/list_border"
+        android:listSelector="@drawable/states_selector_list"
+        />
+
+    <Button
+        android:id="@+id/btn_confirm"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="onConfirm"
+        android:text="@string/btn_txt_confirm"
+        android:layout_alignParentBottom="true"/>
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/content_fw_update.xml b/app/src/main/res/layout/content_fw_update.xml
new file mode 100644 (file)
index 0000000..14ae945
--- /dev/null
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@color/colorFwUpdateBkg"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:context="com.example.ti.oadexample.FwUpdateActivity"
+    tools:showIn="@layout/activity_fw_update"
+    >
+    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        >
+
+        <LinearLayout
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            >
+
+            <!-- connection status -->
+            <LinearLayout
+                android:layout_marginLeft="10dp"
+                android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="left"
+                android:paddingTop="10dp"
+                android:paddingBottom="0dp"
+                android:paddingLeft="6dp"
+                android:paddingRight="6dp">
+                <TextView
+                    style="@style/labelStyle"
+                    android:layout_gravity="left"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:text="@string/label_state"
+                    android:paddingRight="10dp"/>
+
+                <TextView android:id="@+id/connection_state"
+                    style="@style/dataStyle"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:text="@string/disconnected" />
+            </LinearLayout>
+
+            <!-- Image to program -->
+            <RelativeLayout
+                android:layout_margin="10dp"
+                android:background="@drawable/group_box"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingBottom="10dp"
+                android:paddingTop="1dp"
+                >
+
+                <!-- Header -->
+                <TextView android:id="@+id/tv_image_label"
+                          style="@style/labelStyle"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:text="@string/label_new_image"
+                          android:gravity="center_vertical"
+                          android:layout_alignParentTop="true"
+                          android:paddingBottom="10dp"/>
+                <RelativeLayout
+                    android:orientation="horizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_below="@id/tv_image_label">
+
+                    <!-- Selected image -->
+                    <TextView android:id="@+id/tv_new_image"
+                              android:layout_width="wrap_content"
+                              android:layout_height="wrap_content"
+                              android:text=""
+                              android:layout_toLeftOf="@+id/btn_selectImage"
+                              android:layout_alignParentStart="true"
+                              android:layout_centerVertical="true"/>
+
+                    <!-- Select image button -->
+                    <Button android:id="@+id/btn_selectImage"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="@string/select_image"
+                            android:onClick="onSelectImage"
+                            android:layout_alignParentEnd="true"
+                            android:layout_centerVertical="true"/>
+
+                </RelativeLayout>
+            </RelativeLayout>
+
+            <!-- Logging -->
+            <RelativeLayout
+                android:layout_margin="10dp"
+                android:background="@drawable/group_box"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingBottom="10dp"
+                android:paddingTop="1dp" >
+                <TextView
+                    android:id="@+id/tv_label_log"
+                    style="@style/labelStyle"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="left"
+                    android:text="@string/label_log"
+                    android:layout_alignParentTop="true"
+                    android:paddingBottom="10dp"/>
+                <TextView
+                    android:id="@+id/tv_log"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:padding="5dp"
+                    android:scrollbars="vertical"
+                    android:maxLines="10"
+                    android:gravity="bottom"
+                    android:layout_below="@id/tv_label_log"/>
+
+            </RelativeLayout>
+
+
+            <!-- Programming stuff -->
+            <LinearLayout
+                android:background="@drawable/group_box"
+                android:layout_margin="10dp"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingBottom="10dp"
+                android:paddingTop="1dp"
+                >
+
+                <!-- Header -->
+                <TextView
+                    style="@style/labelStyle"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="left"
+                    android:paddingBottom="10dp"
+                    android:text="@string/label_program"/>
+
+                <!-- Safe mode -->
+                <CheckBox
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/safe_mode"
+                    android:id="@+id/cbSafeMode"
+                    android:paddingBottom="5dp"
+                    android:onClick="onSafeMode"
+                    android:textColor="#8A000000"/>
+
+
+                <!-- Block delay -->
+                <LinearLayout
+                    android:orientation="horizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="left"
+                    android:paddingTop="10dp"
+                    android:paddingBottom="10dp"
+                    android:paddingLeft="0dp"
+                    android:paddingRight="6dp">
+
+                    <!-- Header-->
+                    <TextView
+                        android:id="@+id/labelDelay"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:paddingLeft="6dp"
+                        android:text="@string/label_block_delay"
+                        android:layout_below="@+id/cbSafeMode"
+                        android:layout_alignParentEnd="true"
+                        />
+
+                    <!-- Slider -->
+                    <SeekBar
+                        android:id="@+id/sbDelay"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="3"
+                        android:gravity="center_vertical"
+                        android:max="50"
+                        android:progress="20"
+                        />
+
+                    <!-- Value -->
+                    <TextView
+                        android:id="@+id/tvDelay"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="2"
+                        android:paddingLeft="6dp"
+                        android:layout_alignParentEnd="true"
+                        />
+
+                </LinearLayout>
+
+
+                <!-- Line with progress status -->
+                <TextView
+                    android:id="@+id/tv_info"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:layout_marginBottom="5dp"
+                    android:layout_weight="0"
+                    android:gravity="center_horizontal"
+                    android:text=""
+                    android:textSize="14sp" />
+
+                <!-- Progress bar -->
+                <ProgressBar
+                    android:id="@+id/pb_progress"
+                    style="@android:style/Widget.Holo.Light.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="5dp"
+                    android:indeterminate="false"
+                    android:max="100"
+                    android:maxHeight="15dp"
+                    android:minHeight="15dp"
+                    android:progress="0" />
+
+                <!-- Program button -->
+                <Button
+                    android:id="@+id/btn_program"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:onClick="onProgramImage"
+                    android:text="@string/start_prog" />
+
+            </LinearLayout>
+
+        </LinearLayout>
+    </ScrollView>
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
new file mode 100644 (file)
index 0000000..356cfce
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_marginTop="?android:attr/actionBarSize"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+    android:scrollbars="none"
+    android:layout_weight="1">
+    <TableLayout
+        android:id="@+id/devicesFound"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:stretchColumns="1"
+        android:orientation="vertical"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior">
+    </TableLayout>
+</ScrollView>
+
diff --git a/app/src/main/res/layout/element_file.xml b/app/src/main/res/layout/element_file.xml
new file mode 100644 (file)
index 0000000..900b40a
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:padding="18dp">
+    <TextView
+        android:id="@+id/name"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:gravity="left"/>
+
+</RelativeLayout>
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..cde69bc
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..c133a0c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..bfa42f0
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..324e72c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..aee44e1
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644 (file)
index 0000000..0a1f325
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303f9f</color>
+    <color name="colorAccent">#FF4081</color>
+    <color name="primaryLight">#553f51b5</color>
+    <color name="colorFwUpdateBkg">#e1e6e9</color>
+    <color name="colorWhite">#ffffff</color>
+</resources>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644 (file)
index 0000000..812cb7b
--- /dev/null
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="fab_margin">16dp</dimen>
+</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644 (file)
index 0000000..f210418
--- /dev/null
@@ -0,0 +1,43 @@
+<resources>
+    <!-- Main activity -->
+    <string name="app_name">Over-the-Air Download</string>
+    <string name="button_connect">Connect</string>
+
+    <!-- FW Update activity -->
+    <string name="title_activity_fw_update">FwUpdateActivity</string>
+
+    <string name="label_state">State:</string>
+    <string name="disconnected">Disconnected</string>
+    <string name="connected">Connected</string>
+
+    <string name="label_new_image">Image to Program</string>
+    <string name="select_image">Select image...</string>
+
+    <string name="label_program">OAD</string>
+    <string name="safe_mode">Safe mode </string>
+    <string name="label_block_delay">Block delay:</string>
+    <string name="start_prog">Start Programming</string>
+    <string name="cancel">Cancel</string>
+    <!--
+    <string name="prog_ongoing">NB! Not permitted to close this view during OAD transfer</string>
+-->
+    <string name="label_progress">Progress:</string>
+    <string name="label_log">Log</string>
+
+    <string name="oad_dialog_programming_finished">Firmware upgrade finished. The device should disconnect. This will be evident by the device restarting and LED start blinking again.</string>
+
+
+    <string-array name="image_arrays">
+        <item>&lt;no image selected&gt; </item>
+        <item>Example Image</item>
+        <item>Browse...</item>
+    </string-array>
+
+    <!-- File activity -->
+    <string name="title_activity_file">FileActivity</string>
+    <string name="label_directory">Select a file from the external storage: </string>
+    <string name="btn_txt_confirm">Confirm</string>
+
+
+
+</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644 (file)
index 0000000..18e58ec
--- /dev/null
@@ -0,0 +1,40 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+</style>
+
+    <style name="labelStyle">
+        <item name="android:textColor">@color/colorPrimaryDark</item>
+        <item name="android:textSize">18dp</item>
+    </style>
+
+    <style name="dataStyle">
+        <item name="android:textColor">#000</item>
+        <item name="android:textSize">16dp</item>
+    </style>
+
+    <style name="nameStyle">
+        <item name="android:textColor">#000</item>
+        <item name="android:textSize">14dp</item>
+    </style>
+
+    <style name="nameStyleSelected">
+        <item name="android:textColor">#3F51B5</item>
+        <item name="android:textSize">14dp</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+</resources>
diff --git a/build.gradle b/build.gradle
new file mode 100644 (file)
index 0000000..77ce66e
--- /dev/null
@@ -0,0 +1,23 @@
+// 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:2.1.3'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644 (file)
index 0000000..13372ae
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 (file)
index 0000000..3c8634d
--- /dev/null
@@ -0,0 +1,6 @@
+#Wed Aug 17 10:55:21 CEST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644 (file)
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/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
+
+# 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\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+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"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # 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 (file)
index 0000000..8a0b282
--- /dev/null
@@ -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/settings.gradle b/settings.gradle
new file mode 100644 (file)
index 0000000..e7b4def
--- /dev/null
@@ -0,0 +1 @@
+include ':app'