summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--BLE OAD Android Source Code 1_0 manifest.html328
-rw-r--r--app/build.gradle27
-rw-r--r--app/build/outputs/apk/app-debug.apkbin0 -> 1541068 bytes
-rw-r--r--app/src/main/AndroidManifest.xml38
-rw-r--r--app/src/main/java/com/example/ti/oadexample/FileActivity.java260
-rw-r--r--app/src/main/java/com/example/ti/oadexample/FwUpdateActivity.java576
-rw-r--r--app/src/main/java/com/example/ti/oadexample/MainActivity.java690
-rw-r--r--app/src/main/java/com/example/ti/oadexample/OadProcess.java474
-rw-r--r--app/src/main/java/com/example/ti/oadexample/TIOADProfile.java87
-rw-r--r--app/src/main/java/com/example/ti/oadexample/Util.java35
-rw-r--r--app/src/main/res/drawable/gradient_bg.xml9
-rw-r--r--app/src/main/res/drawable/gradient_pressed_bg.xml8
-rw-r--r--app/src/main/res/drawable/group_box.xml9
-rw-r--r--app/src/main/res/drawable/list_border.xml5
-rw-r--r--app/src/main/res/drawable/states_selector_list.xml4
-rw-r--r--app/src/main/res/layout/activity_file.xml18
-rw-r--r--app/src/main/res/layout/activity_fw_update.xml25
-rw-r--r--app/src/main/res/layout/activity_main.xml25
-rw-r--r--app/src/main/res/layout/content_file.xml63
-rw-r--r--app/src/main/res/layout/content_fw_update.xml248
-rw-r--r--app/src/main/res/layout/content_main.xml20
-rw-r--r--app/src/main/res/layout/element_file.xml14
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 3418 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2206 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4842 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 7718 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 10486 bytes
-rw-r--r--app/src/main/res/values/colors.xml9
-rw-r--r--app/src/main/res/values/dimens.xml6
-rw-r--r--app/src/main/res/values/strings.xml43
-rw-r--r--app/src/main/res/values/styles.xml40
-rw-r--r--build.gradle23
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 53636 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rw-r--r--gradlew160
-rw-r--r--gradlew.bat90
-rw-r--r--settings.gradle1
37 files changed, 3341 insertions, 0 deletions
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
index 0000000..16f56f1
--- /dev/null
+++ b/BLE OAD Android Source Code 1_0 manifest.html
@@ -0,0 +1,328 @@
1<!--
2Texas Instruments Manifest Format 2.0
3-->
4
5<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
6<html>
7
8<head>
9<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
10<!-- @Start Style -->
11<!-- Default style in case someone doesnt have Internet Access -->
12<style type="text/css" id="internalStyle">
13 body, div, p {
14 font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif;
15 font-size: 13px;
16 line-height: 1.3;
17 }
18 body {
19 margin: 20px;
20 }
21 h1 {
22 font-size: 150%;
23 }
24 h2 {
25 font-size: 120%;
26 }
27 h3 {
28 font-size: 100%;
29 }
30 img {
31 border: 0px;
32 vertical-align: middle;
33 }
34 table, th, td, tr {
35 border: 1px solid black;
36 font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif;
37 font-size: 13px;
38 line-height: 1.3;
39 empty-cells: show;
40 padding: 5px;
41 }
42 table {
43 border-collapse: collapse;
44 width: 100%;
45 }
46 tr {
47 page-break-inside: avoid;
48 }
49 #TIlogoLeft {
50 background-color: black;
51 padding: 0;
52 width: 20%;
53 }
54 #TIlogoRight {
55 background-color: red;
56 padding: 0;
57 }
58 #ProductName {
59 text-align: center;
60 }
61 #ReleaseDate {
62 text-align: center;
63 }
64 .LogoSection {
65 margin: 0;
66 padding: 0;
67 }
68 .HeaderSection {
69 margin: 25px 0 25px 0;
70 padding: 0;
71 }
72 .LegendSection {
73 margin: 25px 0 25px 0;
74 }
75 .ExportSection {
76 margin: 25px 0 25px 0;
77 }
78 .DisclaimerSection {
79 margin: 25px 0 25px 0;
80 }
81 .CreditSection {
82 margin: 25px 0 25px 0;
83 }
84 .LicenseSection {
85 margin: 25px 0 25px 0;
86 }
87 .ManifestTable {
88 margin: 25px 0 25px 0;
89 }
90</style>
91<!-- Override style from TI if they have Internet Access -->
92<link type="text/css" rel="stylesheet" href="timanifeststyle.css">
93<!-- @End Style -->
94<title>Texas Instruments Manifest</title>
95</head>
96
97<body><!-- Logo display, will need to fix up the URLs, this is just for testing.. Image alternate display not wporking well yet -->
98<div class="LogoSection">
99<table>
100 <tbody>
101 <tr>
102 <td id="TIlogoLeft">
103 <a href="http://www.ti.com/">
104 <!-- img src="tilogo.gif" alt="Texas Instruments Incorporated" -->
105 <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" />
106 </a>
107 </td>
108 <td id="TILogoRight">
109 <!-- img src="titagline.gif" alt="Technology for Innovators(tm)"-->
110 <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=" />
111 </td>
112 </tr>
113 </tbody>
114</table>
115</div><div class="HeaderSection">
116<h1 id="ProductName">
117<!-- @Start Product -->
118BLE OAD Android Source Code Manifest
119<!-- @End Product -->
120</h1>
121
122<h2 id="ReleaseDate">
123<!-- @Start Date -->
12412-16-2016
125<!-- @End Date -->
126</h2>
127
128
129<h2 id="SRASID">
130<!-- @Start Date -->
131Manifest ID - SRAS00003564
132<!-- @End Date -->
133</h2>
134</div><div class="LegendSection">
135<h2>Legend</h2>
136<p>(explanation of the fields in the Manifest Table below)</p>
137<table>
138<tbody>
139<tr>
140<td>
141<b>Software Name </b>
142</td>
143<td>
144The name of the application or file
145</td>
146</tr>
147<tr>
148<td>
149<b>Version</b>
150</td>
151<td>
152Version of the application or file
153</td>
154</tr>
155<tr>
156<td>
157<b>License Type</b>
158</td>
159<td>
160Type of license(s) under which TI will be providing
161software to the licensee (e.g. BSD-3-Clause, GPL-2.0, TI TSPA License, TI
162Commercial License). The license could be under Commercial terms or Open Source. See Open Source Reference License Disclaimer in
163the Disclaimers Section. Whenever possible, TI will use an <a href="http://spdx.org/licenses/"> SPDX Short Identifier </a> for an Open Source
164License. TI Commercial license terms are not usually included in the manifest and are conveyed through a variety
165of means such as a clickwrap license upon install,
166a signed license agreement and so forth.
167</td>
168</tr>
169<tr>
170<td>
171<b>Location</b>
172</td>
173<td>
174The directory name and path on the media or a specific file where the Software is located. Typically fully qualified path names
175are not used and instead the relevant top level directory of the application is given.
176A notation often used in the manifests is [as installed]/directory/*. Note that the asterisk implies that all
177files under that directory are licensed as the License Type field denotes. Any exceptions to this will
178generally be denoted as [as installed]/directory/* except as noted below which means as shown in subsequent rows of
179the manifest.
180</td>
181</tr>
182<tr>
183<td>
184<b>Delivered As</b>
185</td>
186<td>
187This field will either be &#8220;Source&#8221;, &#8220;Binary&#8221; or &#8220;Source
188and Binary&#8221; and is the primary form the content of the Software is delivered
189in. If the Software is delivered in an archive format, this field
190applies to the contents of the archive. If the word Limited is used
191with Source, as in &#8220;Limited Source&#8221; or &#8220;Limited Source and Binary&#8221; then
192only portions of the Source for the application are provided.
193</td>
194</tr>
195<tr>
196<td>
197<b>Modified by TI</b>
198</td>
199<td>
200This field will either be &#8220;Yes&#8221; or &#8220;No&#8221;. A &#8220;Yes&#8221; means
201TI has made changes to the Software. A &#8220;No&#8221; means TI has not made any
202changes. Note: This field is not applicable for Software &#8220;Obtained
203from&#8221; TI.
204</td>
205</tr>
206<tr>
207<td>
208<b>Obtained from</b>
209</td>
210<td>
211This field specifies from where or from whom TI obtained
212the Software. It may be a URL to an Open Source site, a 3<sup>rd</sup>
213party licensor, or TI. See Links Disclaimer in the Disclaimers
214Section.
215</td>
216</tr>
217</tbody>
218</table>
219</div><div class="DisclaimerSection">
220<h2>Disclaimers</h2>
221<h3>Export Control Classification Number (ECCN)</h3>
222<p>Any use of ECCNs listed in the Manifest is at the user&#8217;s risk
223and without recourse to TI. Your
224company, as the exporter of record, is responsible for determining the
225correct classification of any item at
226the time of export. Any export classification by TI of Software is for
227TI&#8217;s internal use only and shall not be construed as a representation
228or warranty
229regarding the proper export classification for such Software or whether
230an export
231license or other documentation is required for exporting such Software</p>
232<h3>Links in the Manifest</h3>
233<p>Any
234links appearing on this Manifest
235(for example in the &#8220;Obtained from&#8221; field) were verified at the time
236the Manifest was created. TI makes no guarantee that any listed links
237will
238remain active in the future.</p>
239<h3>Open Source License References</h3>
240<p>Your company is responsible for confirming the
241applicable license terms for any open source Software
242listed in this Manifest that was not &#8220;Obtained from&#8221; TI. Any open
243source license
244specified in this Manifest for Software that was
245not &#8220;Obtained from&#8221; TI is for TI&#8217;s internal use only and shall not be
246construed as a representation or warranty regarding the proper open
247source license terms
248for such Software.</p>
249</div><div class="ExportSection">
250<h2>Export Information</h2>
251<p>ECCN for Software included in this release:</p>
252Publicly Available - Open Source or TI TSPA License
253</div><div class="ManifestTable">
254<!-- h2>Manifest Table</h2 -->
255
256 <table>
257 <tbody>
258
259 <h2>
260 BLE OAD Android Source Code Manifest Table
261 </h2>
262
263
264 <p>
265
266 See the Legend above for a description of these columns.
267
268 </p>
269
270 <table id="targetpackages" name="targetpackages">
271 <thead>
272 <tr>
273 <td><b>Software Name</b></td>
274 <td><b>Version</b></td>
275 <td><b>License Type</b></td>
276 <td><b>Delivered As</b></td>
277 <td><b>Modified by TI</b></td>
278 <td></td>
279 <td></td>
280 </tr>
281 </thead>
282
283
284 <tbody>
285 <tr>
286 <td id="name" name="name" rowspan="2">
287 BLE OAD Code Example
288 </td>
289 <td id="version" name="version" rowspan="2">
290 1.0
291 </td>
292 <td id="license" name="license" rowspan="2">
293 BSD-3-Clause
294 </td>
295 <td id="delivered" name="delivered" rowspan="2">
296 Source and Binary
297 </td>
298 <td id="modified" name="modified" rowspan="2">
299 N/A
300 </td>
301 <td><b>Location</b></td>
302 <td id="location" name="location">
303 [as installed]/bin
304 </td>
305 </tr>
306 <tr>
307 <td><b>Obtained from</b></td>
308 <td id="obtained" name="obtained">
309 TI
310 </td>
311 </tr>
312
313 </tbody>
314 </table>
315
316 </p>
317 </p>
318 <p>
319
320</div><div class="CreditSection">
321<h2>Credits</h2>
322<BR> <BR><BR><BR><BR>
323</div><div class="LicenseSection">
324<h2>Licenses</h2>
325<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>
326</div>
327
328</body></html> \ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..15e2d0d
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,27 @@
1apply plugin: 'com.android.application'
2
3android {
4 compileSdkVersion 23
5 buildToolsVersion "23.0.3"
6
7 defaultConfig {
8 applicationId "com.example.ti.oadexample"
9 minSdkVersion 21
10 targetSdkVersion 23
11 versionCode 1
12 versionName "1.0"
13 }
14 buildTypes {
15 release {
16 minifyEnabled false
17 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 }
19 }
20}
21
22dependencies {
23 compile fileTree(dir: 'libs', include: ['*.jar'])
24 testCompile 'junit:junit:4.12'
25 compile 'com.android.support:appcompat-v7:23.3.0'
26 compile 'com.android.support:design:23.3.0'
27}
diff --git a/app/build/outputs/apk/app-debug.apk b/app/build/outputs/apk/app-debug.apk
new file mode 100644
index 0000000..7acdabf
--- /dev/null
+++ b/app/build/outputs/apk/app-debug.apk
Binary files differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e35f982
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,38 @@
1<?xml version="1.0" encoding="utf-8"?>
2<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 package="com.example.ti.oadexample">
4
5 <uses-permission android:name="android.permission.BLUETOOTH" />
6 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
7 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
8 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
9
10 <application
11 android:allowBackup="true"
12 android:icon="@mipmap/ic_launcher"
13 android:label="@string/app_name"
14 android:supportsRtl="true"
15 android:theme="@style/AppTheme">
16 <activity
17 android:name=".MainActivity"
18 android:label="@string/app_name"
19 android:theme="@style/AppTheme.NoActionBar">
20 <intent-filter>
21 <action android:name="android.intent.action.MAIN" />
22 <category android:name="android.intent.category.LAUNCHER" />
23 </intent-filter>
24 </activity>
25 <activity
26 android:name=".FwUpdateActivity"
27 android:label="@string/title_activity_fw_update"
28 android:parentActivityName=".MainActivity"
29 android:theme="@style/AppTheme.NoActionBar"
30 android:configChanges="orientation|screenSize"/>
31 <activity
32 android:name=".FileActivity"
33 android:label="@string/title_activity_file"
34 android:theme="@style/AppTheme.NoActionBar"
35 android:windowSoftInputMode="stateHidden" ></activity>
36 </application>
37
38</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
index 0000000..bd01be4
--- /dev/null
+++ b/app/src/main/java/com/example/ti/oadexample/FileActivity.java
@@ -0,0 +1,260 @@
1package com.example.ti.oadexample;
2
3import android.content.Context;
4import android.content.DialogInterface;
5import android.content.Intent;
6import android.os.Bundle;
7import android.os.Environment;
8import android.support.v4.widget.TextViewCompat;
9import android.support.v7.app.AlertDialog;
10import android.support.v7.app.AppCompatActivity;
11import android.util.Log;
12import android.view.LayoutInflater;
13import android.view.View;
14import android.view.ViewGroup;
15import android.widget.AdapterView;
16import android.widget.AdapterView.OnItemClickListener;
17import android.widget.BaseAdapter;
18import android.widget.Button;
19import android.widget.EditText;
20import android.widget.ListView;
21import android.widget.TextView;
22import android.widget.Toast;
23
24import java.io.File;
25import java.io.FilenameFilter;
26import java.util.ArrayList;
27import java.util.List;
28
29
30public class FileActivity extends AppCompatActivity {
31
32 public final static String EXTRA_FILENAME = "com.example.ti.oadexample.FILENAME";
33 private static final String TAG = "FileActivity";
34
35 // GUI
36 private FileAdapter mFileAdapter;
37 private ListView mLvFileList;
38 private EditText mEtDirName;
39 private Button mBtnConfirm;
40
41 // Housekeeping
42 private String mSelectedFile;
43 private List<String> mFileList;
44 private File mDir;
45
46 @Override
47 public void onCreate(Bundle savedInstanceState) {
48 super.onCreate(savedInstanceState);
49 setContentView(R.layout.activity_file);
50
51 // Set default directory
52 mDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
53
54 // Initialize GUI elements
55 mEtDirName = (EditText) findViewById(R.id.et_directory);
56 mBtnConfirm = (Button) findViewById(R.id.btn_confirm);
57 mLvFileList = (ListView) findViewById(R.id.lv_file);
58 mLvFileList.setOnItemClickListener(mFileClickListener);
59
60 // Display path in GUI
61 mEtDirName.setText(mDir.getAbsolutePath());
62 mEtDirName.setSelection(mEtDirName.getText().length());
63
64 // Display files found in path
65 populateFileList();
66
67 }
68 @Override
69 public void onDestroy() {
70 mFileList = null;
71 mFileAdapter = null;
72 super.onDestroy();
73 }
74
75
76 /**
77 * Function called when the user reloads the file directory
78 */
79 public void onDirChanged(View view) {
80 // Save the new directory and populate the list view
81 mDir = new File(mEtDirName.getText().toString());
82 populateFileList();
83 }
84
85 /**
86 * Listener for file click
87 */
88 private OnItemClickListener mFileClickListener = new AdapterView.OnItemClickListener() {
89 public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
90
91 // A file item has been selected
92 mFileAdapter.setSelectedPosition(pos);
93 }
94 };
95
96 /**
97 * Callback for confirm button
98 */
99 public void onConfirm(View v) {
100 Intent i = new Intent();
101
102 // Save selected file (if any) and finish activity
103 if (mFileList.size() > 0) {
104 i.putExtra(EXTRA_FILENAME, mDir.getAbsolutePath() + File.separator + mSelectedFile);
105 setResult(RESULT_OK, i);
106 } else {
107 setResult(RESULT_CANCELED, i);
108 }
109 finish();
110 }
111
112 /**
113 * FileAdapter class: Handle the file list
114 */
115 class FileAdapter extends BaseAdapter {
116 Context mContext;
117 List<String> mFiles;
118 LayoutInflater mInflater;
119 int mSelectedPos;
120
121 public FileAdapter(Context context, List<String> files) {
122 mInflater = LayoutInflater.from(context);
123 mContext = context;
124 mFiles = files;
125 mSelectedPos = 0;
126 }
127
128 @Override
129 public int getCount() {
130 return mFiles.size();
131 }
132
133 @Override
134 public Object getItem(int pos) {
135 return mFiles.get(pos);
136 }
137
138 @Override
139 public long getItemId(int pos) {
140 return pos;
141 }
142
143 @Override
144 public View getView(int pos, View view, ViewGroup parent) {
145 ViewGroup vg;
146
147 if (view != null) {
148 vg = (ViewGroup) view;
149 } else {
150 vg = (ViewGroup) mInflater.inflate(R.layout.element_file, null);
151 }
152
153 // Grab file object
154 String file = mFiles.get(pos);
155
156 // Show file name
157 TextView tvName = (TextView) vg.findViewById(R.id.name);
158 tvName.setText(file);
159
160 // Highlight selected object
161 if (pos == mSelectedPos) {
162 TextViewCompat.setTextAppearance(tvName, R.style.nameStyleSelected);
163 } else {
164 TextViewCompat.setTextAppearance(tvName, R.style.nameStyle);
165 }
166
167 return vg;
168 }
169
170 /**
171 * Function called when a file has been selected.
172 */
173 public void setSelectedPosition(int pos) {
174 mSelectedFile = mFileList.get(pos);
175 mSelectedPos = pos;
176 notifyDataSetChanged();
177 }
178
179 }
180
181 /**
182 * List all .bin files located at the selected directory
183 */
184 public void populateFileList()
185 {
186 // Create a list of files
187 mFileList = new ArrayList<>();
188 mFileAdapter = new FileAdapter(this, mFileList);
189 mLvFileList.setAdapter(mFileAdapter);
190
191 if (mDir.exists() && mDir.canRead()) {
192 if(Util.DEBUG) Log.d(TAG, mDir.getPath());
193
194 // Create filter on .bin files
195 FilenameFilter textFilter = new FilenameFilter()
196 {
197 public boolean accept(File dir, String name)
198 {
199 String lowercaseName = name.toLowerCase();
200 if (lowercaseName.endsWith(".bin"))
201 {
202 return true;
203 }
204 else
205 {
206 return false;
207 }
208 }
209 };
210
211 // Create array of all .bin files
212 File[] files = mDir.listFiles(textFilter);
213 if (files == null) {
214 // Show error dialog
215 AlertDialog.Builder builder = new AlertDialog.Builder(this);
216 builder.setTitle("Error")
217 .setMessage("Could not access internal file storage.")
218 .setCancelable(false)
219 .setPositiveButton("OK",
220 new DialogInterface.OnClickListener() {
221 public void onClick(DialogInterface dialog, int id) {
222 finish();
223 }
224 })
225 .show();
226 return;
227 }
228
229 for (File file : files)
230 {
231 if (!file.isDirectory())
232 {
233 mFileList.add(file.getName());
234 }
235 }
236
237 if (mFileList.size() == 0)
238 {
239 Toast.makeText(this, "No OAD images available", Toast.LENGTH_LONG).show();
240 }
241 }
242 else
243 {
244 Toast.makeText(this, Environment.DIRECTORY_DOWNLOADS + " does not exist or is not readable", Toast.LENGTH_LONG).show();
245 }
246
247 // Select the first item as default
248 if (mFileList.size() > 0)
249 {
250 mFileAdapter.setSelectedPosition(0);
251 mBtnConfirm.setText("Confirm");
252 }
253 else
254 {
255 mBtnConfirm.setText("Cancel");
256 }
257
258 }
259
260}
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
index 0000000..92fc4d0
--- /dev/null
+++ b/app/src/main/java/com/example/ti/oadexample/FwUpdateActivity.java
@@ -0,0 +1,576 @@
1package com.example.ti.oadexample;
2
3import android.app.AlertDialog;
4import android.bluetooth.BluetoothGatt;
5import android.bluetooth.BluetoothGattCharacteristic;
6import android.bluetooth.BluetoothGattService;
7import android.content.BroadcastReceiver;
8import android.content.Context;
9import android.content.DialogInterface;
10import android.content.Intent;
11import android.content.IntentFilter;
12import android.os.Build;
13import android.os.Bundle;
14import android.support.v7.app.AppCompatActivity;
15import android.support.v7.widget.Toolbar;
16import android.text.Html;
17import android.text.method.ScrollingMovementMethod;
18import android.util.Log;
19import android.view.MenuItem;
20import android.view.View;
21import android.widget.Button;
22import android.widget.CheckBox;
23import android.widget.ProgressBar;
24import android.widget.SeekBar;
25import android.widget.TextView;
26import android.widget.Toast;
27
28import java.util.HashMap;
29import java.util.List;
30import java.util.UUID;
31
32public class FwUpdateActivity extends AppCompatActivity {
33
34 public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME";
35
36 // Locals
37 private String mDeviceName;
38 private MainActivity mMainActivity;
39 private static final short MIN_BLOCK_DELAY = 10; // make sure block delay is larger than connection interval
40
41 // Tag used for logging
42 private static final String TAG = "FwUpdateActivity";
43
44 // GUI
45 private TextView mUpdateImage;
46 private TextView mConnectionState;
47 private TextView mLog;
48 private Button mBtnProgram;
49 private Button mBtnSelectImage;
50 private ProgressBar mProgressBar;
51 private TextView mProgressInfo;
52 private SeekBar mSbDelay;
53 private TextView mTvDelay;
54
55 // BLE
56 private HashMap<String, BluetoothGattCharacteristic> mGattCharacteristics = new HashMap<>();
57 public BluetoothGattCharacteristic mCharIdentify = null;
58 public BluetoothGattCharacteristic mCharBlock = null;
59 private BluetoothGattCharacteristic mCharConnReq = null;
60 private BluetoothGattCharacteristic mCharTestResult = null;
61 public BluetoothGattCharacteristic mCharImageStatus = null;
62 public BluetoothGattCharacteristic getCharConnReq(){
63 return mCharConnReq;
64 }
65
66 // Programming
67 public int mBlockDelay = 0;
68
69 // Housekeeping
70 boolean mIsConnected = false;
71 private boolean mTestOK = false;
72 private boolean mOadFailedAlertShown = false;
73 boolean mSafeMode = false;
74
75 // Request code for file activity
76 public static final int FILE_ACTIVITY_REQUEST = 0;
77
78 // Class object to handle the OAD process
79 OadProcess mOadProcess = null;
80
81 @Override
82 protected void onCreate(Bundle savedInstanceState) {
83 super.onCreate(savedInstanceState);
84 setContentView(R.layout.activity_fw_update);
85 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
86 setSupportActionBar(toolbar);
87
88 // Add back-button to parent activity
89 getSupportActionBar().setDisplayHomeAsUpEnabled(true);
90
91 // Get name of device that this intent is opened for
92 Intent intent = getIntent();
93 mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME);
94
95 // Set activity title to the device name
96 setTitle(mDeviceName);
97
98 // Get UI elements
99 mProgressInfo = (TextView) findViewById(R.id.tv_info);
100 mConnectionState = (TextView) findViewById(R.id.connection_state);
101 mUpdateImage = (TextView) findViewById(R.id.tv_new_image);
102 mLog = (TextView) findViewById(R.id.tv_log);
103 mBtnProgram = (Button) findViewById(R.id.btn_program);
104 mBtnSelectImage = (Button) findViewById(R.id.btn_selectImage);
105 mProgressBar = (ProgressBar) findViewById(R.id.pb_progress);
106 mSbDelay = (SeekBar) findViewById(R.id.sbDelay);
107 mTvDelay = (TextView) findViewById(R.id.tvDelay);
108
109 // Initialize UI elements
110 mBtnProgram.setEnabled(false);
111 mBtnSelectImage.setEnabled(false);
112 mUpdateImage.setText("");
113 mTvDelay.setText(getDelayText());
114 mBlockDelay = mSbDelay.getProgress() + MIN_BLOCK_DELAY;
115 mSbDelay.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){
116
117 @Override
118 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
119 mBlockDelay = progress + MIN_BLOCK_DELAY; // adding offset because delay must be larger than connection interval
120 mTvDelay.setText(String.valueOf(mBlockDelay) + " ms");
121 }
122
123
124 @Override
125 public void onStartTrackingTouch(SeekBar seekBar) {
126 }
127
128 @Override
129 public void onStopTrackingTouch(SeekBar seekBar) {
130 }
131 });
132 mLog.setMovementMethod(new ScrollingMovementMethod());
133
134 // Get instance of main activity
135 mMainActivity = (MainActivity) MainActivity.activity;
136 }
137
138 @Override
139 protected void onResume() {
140 super.onResume();
141 // Register a receiver for broadcast updates
142 registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
143 }
144
145 @Override
146 protected void onPause() {
147 super.onPause();
148 // Do not receive broadcast updates when paused
149 unregisterReceiver(mGattUpdateReceiver);
150 }
151
152 @Override
153 protected void onDestroy() {
154 super.onDestroy();
155 mMainActivity = null;
156 }
157
158 @Override
159 public boolean onOptionsItemSelected(MenuItem item) {
160 switch (item.getItemId()) {
161 case android.R.id.home:
162 // Disconnect when home button is pressed
163 Toast.makeText(getApplicationContext(),"Disconnecting", Toast.LENGTH_LONG).show();
164 mMainActivity.disconnect();
165 finish();
166 break;
167 }
168 return true;
169 }
170
171 @Override
172 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
173 // Check which request we are responding to
174 if (requestCode == FILE_ACTIVITY_REQUEST) {
175 // Make sure the request was successful
176 if (resultCode == RESULT_OK) {
177 String filename = data.getStringExtra(FileActivity.EXTRA_FILENAME);
178 loadFile(filename);
179 }
180 }
181 }
182
183 /**
184 * Method to create the text field for the delay slider
185 * @return text to add next to the slider
186 */
187 public String getDelayText(){
188 return String.valueOf(mSbDelay.getProgress() + MIN_BLOCK_DELAY) + " ms";
189 }
190
191 /**
192 * Function called when the checkbox for selecting safe mode is clicked
193 */
194 public void onSafeMode(View view) {
195
196 // Is the safe mode selected?
197 mSafeMode = ((CheckBox) view).isChecked();
198
199 // Disable block delay slider if safe mode selected
200 mSbDelay.setEnabled(!mSafeMode);
201 if(mSafeMode) {
202 mTvDelay.setText("N/A");
203 }
204 else{
205 mTvDelay.setText(getDelayText());
206 }
207 }
208
209
210 /**
211 * Function called when the user clicks on the select image button
212 */
213 public void onSelectImage(View v) {
214 Intent i = new Intent(this, FileActivity.class);
215 startActivityForResult(i, FILE_ACTIVITY_REQUEST);
216 }
217
218 /**
219 * Function called when user clicks the program/cancel button
220 *
221 * @param v
222 */
223 public void onProgramImage(View v) {
224 if (isProgramming()) {
225 mOadProcess.stopProgramming();
226 } else {
227 // Display warning if not connected
228 if(!mIsConnected)
229 {
230 Toast.makeText(getApplicationContext(),"Device not connect. Please try to re-connect.", Toast.LENGTH_LONG).show();
231 return;
232 }
233 // Start programming
234 mOadFailedAlertShown = false;
235 mOadProcess.startProgramming();
236 }
237 }
238
239 /**
240 * Update GUI based on programming status
241 */
242 public void updateGui(final boolean programming) {
243
244 FwUpdateActivity.this.runOnUiThread(new Runnable() {
245 @Override
246 public void run() {
247 if (programming) {
248 // Busy: show cancel button, disable file selector
249 mBtnProgram.setText(R.string.cancel);
250 mBtnSelectImage.setEnabled(false);
251 } else {
252 // Idle: no progress, show program button, enable file selector if connected
253 mProgressBar.setProgress(0);
254 mBtnProgram.setText(R.string.start_prog);
255 mBtnSelectImage.setEnabled(true);
256 }
257
258
259 }
260 });
261
262 }
263
264 /**
265 * Display progress info
266 */
267 public void displayProgressText(String txt)
268 {
269 mProgressInfo.setText(txt);
270 }
271
272 /**
273 * Set progress bar value
274 */
275 public void updateProgressBar(int progress)
276 {
277 mProgressBar.setProgress(progress);
278 }
279
280 /**
281 * Append/Set log text
282 *
283 * @param txt Text to write to log window
284 * @param append If true, the text will be appended to existing test, otherwise
285 * it old text will be overridden.
286 */
287 public void log(CharSequence txt, boolean append){
288 if(append){
289 mLog.append(txt);
290 }
291 else{
292 mLog.setText(txt);
293 }
294 }
295
296 /**
297 * Check if programming is in progress
298 */
299 private boolean isProgramming() {
300 if ((mOadProcess != null) && mOadProcess.isProgramming()){
301 return true;
302 }
303 return false;
304 }
305
306 /**
307 * Function called when the user has chosen a file
308 */
309 private boolean loadFile(String filepath) {
310 int readLen = 0;
311
312 // Check self test result
313 if (mTestOK != true) {
314 AlertDialog.Builder testFailedAlertDialog = new AlertDialog.Builder(this);
315 testFailedAlertDialog.setTitle("Error");
316 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.");
317 testFailedAlertDialog.setNegativeButton("OK", null);
318 (testFailedAlertDialog.create()).show();
319 return false;
320 }
321
322 // Create OAD process object
323 if(mOadProcess == null)
324 {
325 mOadProcess = OadProcess.newInstance(mMainActivity, this);
326 }
327
328 // Read file
329 readLen = mOadProcess.readFile(filepath);
330 if(readLen == -1){
331 mLog.setText("Failed to read image : " + filepath + "\n");
332 return false;
333 }
334
335 // Configure connection parameters
336 if ( Build.VERSION.SDK_INT >= 21)
337 {
338 if (Util.DEBUG) Log.d(TAG,"Requested connection priority HIGH, result : " + mMainActivity.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH));
339 }
340 mOadProcess.setConnectionParameters();
341
342 // Update GUI elements
343 mUpdateImage.setText(filepath);
344 mBtnProgram.setEnabled(true);
345 updateGui(isProgramming());
346
347 // Log
348 mLog.setText("Image to program : " + filepath + "\n");
349 mLog.append("File size : " + readLen + " bytes (" + (readLen / 16) + ") blocks\n");
350 mLog.append("Ready to program device!\n");
351
352 return true;
353 }
354
355 /**
356 * Update the connection status field
357 */
358 private void updateConnectionState(final int resourceId) {
359 FwUpdateActivity.this.runOnUiThread(new Runnable() {
360 @Override
361 public void run() {
362 mConnectionState.setText(resourceId);
363
364 // Enable image selection when connected
365 if(mIsConnected){
366 mBtnSelectImage.setEnabled(true);
367 }
368 }
369 });
370 }
371
372
373 private void updateFlashSelfTestState(final byte[] value) {
374 FwUpdateActivity.this.runOnUiThread(new Runnable() {
375 @Override
376 public void run() {
377 if ((value[0] & 0xFF) == 0x01) {
378 mLog.append(Html.fromHtml(String.format("<font color=#00CC00>FLASH Self test passed !</font>")));
379 mTestOK = true;
380 }
381 else {
382 mLog.append(Html.fromHtml(String.format("<font color=#CC0000>FLASH Self test failed !</font>")));
383 mTestOK = false;
384 }
385 }
386 });
387 }
388
389 /**
390 * Create an intent filter for actions broadcast
391 * by MainActivity
392 *
393 * @return The created IntentFilter object
394 */
395 private static IntentFilter makeGattUpdateIntentFilter() {
396 final IntentFilter intentFilter = new IntentFilter();
397 intentFilter.addAction(MainActivity.ACTION_GATT_CONNECTED);
398 intentFilter.addAction(MainActivity.ACTION_GATT_DISCONNECTED);
399 intentFilter.addAction(MainActivity.ACTION_GATT_SERVICES_DISCOVERED);
400 intentFilter.addAction(MainActivity.ACTION_DATA_NOTIFY);
401 intentFilter.addAction(MainActivity.ACTION_DATA_READ);
402 return intentFilter;
403 }
404
405 /**
406 * Iterate through the supported GATT Services/Characteristics,
407 * and initialize UI elements displaying them.
408 */
409 private void initializeGattServiceUIElements(final Context context, List<BluetoothGattService> gattServices) {
410
411 // Loop through services
412 boolean serviceFound = false;
413 for (BluetoothGattService s : gattServices) {
414 if(TIOADProfile.isOadService(s))
415 {
416 // OAD service found
417 if (Util.INFO) Log.i(TAG,"Found TI OAD Service");
418 serviceFound = true;
419
420 // Save characteristics
421 mCharIdentify = s.getCharacteristic(UUID.fromString(TIOADProfile.OAD_IMAGE_NOTIFY_CHAR));
422 mCharBlock = s.getCharacteristic(UUID.fromString(TIOADProfile.OAD_BLOCK_REQUEST_CHAR));
423 mCharBlock.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
424 mCharImageStatus = s.getCharacteristic(UUID.fromString(TIOADProfile.OAD_IMAGE_STATUS_CHAR));
425 }
426 else if (TIOADProfile.isConnectionControlService(s)) {
427 mCharConnReq = s.getCharacteristic(UUID.fromString(TIOADProfile.REQUEST_CONNECTION_PARAMETERS_CHAR));
428 }
429 else if(TIOADProfile.isTestService(s)){
430 // Test service found, save characteristics
431 mCharTestResult = s.getCharacteristic(UUID.fromString(TIOADProfile.TEST_DATA_CHAR));
432
433 // Read test result to check if external flash is ok
434 mMainActivity.readCharacteristic(mCharTestResult);
435 }
436 }
437
438 // Check if self test result is present
439 if (mCharTestResult == null) {
440 // Test service is missing, so we cannot check, print a warning in the text field.
441 mLog.append(Html.fromHtml(String.format("<font color=#CC0000>No test service on current device, cannot check if external flash is working !</font>")));
442 mTestOK = true;
443 }
444
445 // Verify that OAD service was found
446 if(!serviceFound)
447 {
448 FwUpdateActivity.this.runOnUiThread(new Runnable() {
449 @Override
450 public void run()
451 {
452 // Servie is missing, display an error
453 AlertDialog.Builder builder = new AlertDialog.Builder(context);
454 builder.setTitle("Error!");
455 builder.setMessage("The expected OAD service was not discovered for current device. " +
456 "The device will be disconnected.");
457 builder.setPositiveButton("OK", new DialogInterface.OnClickListener()
458 {
459 @Override
460 public void onClick(DialogInterface dialog, int which)
461 {
462 // Disconnect and return from activity
463 mMainActivity.disconnect();
464 finish();
465 }
466 });
467 AlertDialog a = builder.create();
468 a.show();
469 }
470 });
471 return;
472 }
473 }
474
475 /**
476 * Handles various events fired by MainActivity
477 * ACTION_GATT_CONNECTED: connected to a GATT server.
478 * ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
479 * ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
480 * ACTION_DATA_NOTIFY: characteristic notification received.
481 * ACTION_DATA_READ: characteristic have been read.
482 */
483 private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
484
485 @Override
486 public void onReceive(Context context, Intent intent) {
487 final String action = intent.getAction();
488 byte [] value;
489 String uuidStr;
490 switch(action){
491 case MainActivity.ACTION_GATT_CONNECTED:
492 mIsConnected = true;
493 updateConnectionState(R.string.connected);
494 break;
495 case MainActivity.ACTION_GATT_DISCONNECTED:
496 mIsConnected = false;
497 updateConnectionState(R.string.disconnected);
498 break;
499 case MainActivity.ACTION_GATT_SERVICES_DISCOVERED:
500 initializeGattServiceUIElements(context, mMainActivity.getSupportedGattServices());
501 break;
502 case MainActivity.ACTION_DATA_NOTIFY:
503 value = intent.getByteArrayExtra(MainActivity.EXTRA_DATA);
504 uuidStr = intent.getStringExtra(MainActivity.EXTRA_UUID);
505 if (uuidStr.equals(mCharIdentify.getUuid().toString())) {
506 // Image verification failed when this notification is received. Create message.
507 if (Util.ERROR) Log.e(TAG, "Image verification failed");
508 AlertDialog.Builder alertDialog = new AlertDialog.Builder(FwUpdateActivity.this);
509 alertDialog.setTitle("Error");
510 alertDialog.setMessage("Image verification failed, cannot do OAD with this image.");
511 alertDialog.setNegativeButton("OK", null);
512 (alertDialog.create()).show();
513
514 // Disable program button
515 if (isProgramming()) {
516 mOadProcess.stopProgramming();
517 }
518
519 }
520 else if (uuidStr.equals(mCharBlock.getUuid().toString())) {
521 // Block request received
522 String block = String.format("%02x%02x",value[1],value[0]);
523 if (Util.DEBUG) Log.d(TAG, "Received block req: " + block);
524 if(mSafeMode) {
525 mOadProcess.programBlock();
526 }
527 }
528 else if(uuidStr.equals(mCharImageStatus.getUuid().toString())){
529 if((value[0] != 0) && !mOadFailedAlertShown){
530 // Oad error status received. Create dialog
531 String msg = "";
532 switch(value[0]){
533 case 1:
534 msg = "The downloaded image’s CRC doesn’t match the one expected from the metadata.";
535 break;
536 case 2:
537 msg = "The external flash cannot be opened.";
538 break;
539 case 3:
540 msg = "A buffer overflow has occurred. Please try OAD in safe mode or with a longer " +
541 "block delay.";
542 break;
543 default:
544 break;
545 }
546 mOadFailedAlertShown = true;
547 AlertDialog.Builder alertDialog = new AlertDialog.Builder(FwUpdateActivity.this);
548 alertDialog.setTitle("Error");
549 alertDialog.setMessage(String.format("OAD programming failed with status %02x: %s",value[0], msg));
550 alertDialog.setNegativeButton("OK", null);
551 (alertDialog.create()).show();
552 // Stop ongoing programming
553 if (isProgramming()) {
554 mOadProcess.stopProgramming();
555 }
556 }
557 }
558 break;
559 case MainActivity.ACTION_DATA_READ:
560 value = intent.getByteArrayExtra(MainActivity.EXTRA_DATA);
561 uuidStr = intent.getStringExtra(MainActivity.EXTRA_UUID);
562 if (uuidStr.toString().compareTo(TIOADProfile.TEST_DATA_CHAR) == 0) {
563 // Test result data received, check data
564 if (value.length > 0) {
565 if (Util.DEBUG) Log.d(TAG, "Read from " + uuidStr + " data =" + value[0]);
566 updateFlashSelfTestState(value);
567 }
568 }
569 break;
570 default:
571 break;
572 }
573 }
574 };
575
576}
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
index 0000000..399291c
--- /dev/null
+++ b/app/src/main/java/com/example/ti/oadexample/MainActivity.java
@@ -0,0 +1,690 @@
1package com.example.ti.oadexample;
2
3import android.Manifest;
4import android.app.Activity;
5import android.app.AlertDialog;
6import android.bluetooth.BluetoothAdapter;
7import android.bluetooth.BluetoothDevice;
8import android.bluetooth.BluetoothGatt;
9import android.bluetooth.BluetoothGattCallback;
10import android.bluetooth.BluetoothGattCharacteristic;
11import android.bluetooth.BluetoothGattDescriptor;
12import android.bluetooth.BluetoothGattService;
13import android.bluetooth.BluetoothManager;
14import android.bluetooth.BluetoothProfile;
15import android.bluetooth.le.BluetoothLeScanner;
16import android.bluetooth.le.ScanCallback;
17import android.bluetooth.le.ScanFilter;
18import android.bluetooth.le.ScanResult;
19import android.bluetooth.le.ScanSettings;
20import android.content.Context;
21import android.content.DialogInterface;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.os.Build;
25import android.support.v4.app.ActivityCompat;
26import android.support.v4.content.ContextCompat;
27import android.support.v7.app.AppCompatActivity;
28import android.os.Bundle;
29import android.support.v7.widget.Toolbar;
30import android.util.Log;
31import android.view.Gravity;
32import android.view.View;
33import android.widget.Button;
34import android.widget.TableLayout;
35import android.widget.TableRow;
36import android.widget.TextView;
37
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.LinkedList;
41import java.util.List;
42import java.util.Map;
43import java.util.Queue;
44import java.util.UUID;
45
46public class MainActivity extends AppCompatActivity {
47
48 // Locals
49 private boolean mScanning = false;
50 private BluetoothAdapter mBluetoothAdapter;
51 private BluetoothLeScanner mLEScanner;
52 private ScanSettings mScanSettings;
53 private BluetoothGatt mBluetoothGatt;
54 private ArrayList<ScanFilter> mScanFilters = new ArrayList<>();
55 private Map<BluetoothDevice, Integer> mBtDevices = new HashMap<>();
56 private TableLayout mTableDevices;
57
58 // Bluetooth SIG identifiers
59 public static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
60
61 // Main Activity Object
62 public static Activity activity = null;
63
64 // Tag used for logging
65 private static final String TAG = "MainActivity";
66
67 // Request codes
68 private final static int MY_PERMISSIONS_REQUEST_ENABLE_BT = 1;
69 private final static int MY_PERMISSIONS_REQUEST_MULTIPLE= 2;
70
71 // Intent actions
72 public final static String ACTION_GATT_CONNECTED =
73 "com.example.ti.oadexample.ACTION_GATT_CONNECTED";
74 public final static String ACTION_GATT_DISCONNECTED =
75 "com.example.ti.oadexample.ACTION_GATT_DISCONNECTED";
76 public final static String ACTION_GATT_SERVICES_DISCOVERED =
77 "com.example.ti.oadexample.ACTION_GATT_SERVICES_DISCOVERED";
78 public final static String ACTION_DATA_NOTIFY = "com.example.ti.oadexample.ACTION_DATA_NOTIFY";
79 public final static String ACTION_DATA_READ = "com.example.ti.oadexample.ACTION_DATA_READ";
80 public final static String ACTION_DATA_WRITE = "com.example.ti.oadexample.ACTION_DATA_WRITE";
81
82 // Intent extras
83 public final static String EXTRA_DATA = "com.example.ti.odaexample.EXTRA_DATA";
84 public final static String EXTRA_UUID = "com.example.ti.odaexample.EXTRA_UUID";
85
86 // Que system for descriptor and characteristic actions;
87 private Queue<BleRequest> characteristicQueue = new LinkedList<>();
88 public enum BleRequestOperation {
89 write,
90 read,
91 enableNotification,
92 }
93 public class BleRequest {
94 public int id;
95 public BluetoothGattDescriptor descriptor;
96 public BluetoothGattCharacteristic characteristic;
97 public BleRequestOperation operation;
98 }
99
100 @Override
101 protected void onCreate(Bundle savedInstanceState) {
102 super.onCreate(savedInstanceState);
103 setContentView(R.layout.activity_main);
104 activity = this;
105
106 // Get UI elements
107 mTableDevices = (TableLayout) findViewById(R.id.devicesFound);
108 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
109 setSupportActionBar(toolbar);
110
111 // Initializes Bluetooth adapter.
112 final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
113 mBluetoothAdapter = bluetoothManager.getAdapter();
114
115 // For Android M: Check permissions
116 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
117 {
118 int storageAccess = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
119 int locationAccess = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION);
120
121 // App needs location permission for BLE scanning, and external storage access for
122 // loading OAD images
123 if((locationAccess != PackageManager.PERMISSION_GRANTED) ||
124 (storageAccess != PackageManager.PERMISSION_GRANTED)){
125 requestPermissions(new String[]{
126 Manifest.permission.ACCESS_COARSE_LOCATION,
127 Manifest.permission.READ_EXTERNAL_STORAGE},
128 MY_PERMISSIONS_REQUEST_MULTIPLE);
129
130 }
131 }
132
133 // Configure default scan filter
134 ScanFilter filter = new ScanFilter.Builder().build();
135 mScanFilters.add(filter);
136
137 // Configure default scan settings
138 mScanSettings = new ScanSettings.Builder().build();
139 }
140
141 @Override
142 protected void onResume() {
143 super.onResume();
144
145 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
146 // Bluetooth is disabled or not available. Display
147 // a dialog requesting user permission to enable Bluetooth.
148 Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
149 startActivityForResult(enableBtIntent, MY_PERMISSIONS_REQUEST_ENABLE_BT);
150 } else if (!mScanning) {
151 // Start scanning
152 mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
153 scanLeDevice(true);
154 }
155 }
156
157 @Override
158 protected void onPause() {
159 super.onPause();
160 if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled() && mScanning) {
161 // Stop scanning
162 scanLeDevice(false);
163 }
164 }
165
166 @Override
167 public void onRequestPermissionsResult(int requestCode,
168 String permissions[], int[] grantResults) {
169 // Callback have been received from a permission request
170 switch (requestCode) {
171 case MY_PERMISSIONS_REQUEST_MULTIPLE:
172 if((permissions[0] == Manifest.permission.ACCESS_COARSE_LOCATION )
173 && (grantResults[0] != PackageManager.PERMISSION_GRANTED)) {
174
175 // Access location was not granted. Display a warning.
176 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
177 builder.setTitle("Functionality limited");
178 builder.setMessage("Since location access has not been granted, this app will not display any bluetooth scan results.");
179 builder.setPositiveButton(android.R.string.ok, null);
180 builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
181 @Override
182 public void onDismiss(DialogInterface dialog) {
183 }
184 });
185 builder.show();
186 }
187 if((permissions.length > 1) && (permissions[1] == Manifest.permission.READ_EXTERNAL_STORAGE )
188 && (grantResults[1] != PackageManager.PERMISSION_GRANTED)) {
189
190 // External storage access was not granted. Display a warning.
191 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
192 builder.setTitle("Functionality limited");
193 builder.setMessage("The app won't be able to load an OAD image, since external storage access not has been granted.");
194 builder.setPositiveButton(android.R.string.ok, null);
195 builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
196 @Override
197 public void onDismiss(DialogInterface dialog) {
198 }
199 });
200 builder.show();
201 }
202 break;
203 }
204 }
205
206 @Override
207 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
208 super.onActivityResult(requestCode, resultCode, data);
209 if (requestCode == MY_PERMISSIONS_REQUEST_ENABLE_BT) {
210 if (resultCode == Activity.RESULT_CANCELED) {
211 // Bluetooth was not enabled, end activity
212 finish();
213 return;
214 }
215 }
216 }
217
218 /**
219 * Connect to a bluetooth device. The connection result is reported asynchronously through the
220 * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
221 * callback.
222 *
223 * @param btDevice instance of device to connect to.
224 */
225 public void connectToDevice(BluetoothDevice btDevice) {
226
227 if (mBluetoothGatt != null) {
228 mBluetoothGatt.close();
229 mBluetoothGatt = null;
230 }
231
232 mBluetoothGatt = btDevice.connectGatt(this, false, mGattCallback);
233
234 // Stop scanning
235 if (mScanning) {
236 scanLeDevice(false);
237 }
238 }
239
240 /**
241 * Disconnect from a device. The disconnection result
242 * is reported asynchronously through the
243 * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
244 * callback.
245 */
246 public void disconnect() {
247 if (mBluetoothAdapter == null || mBluetoothGatt == null) {
248 if (Util.WARN) Log.w(TAG, "Bluetooth not initialized");
249 return;
250 }
251 mBluetoothGatt.disconnect();
252 }
253
254 /**
255 * Retrieve a list of supported GATT services on the connected device. This should be
256 * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
257 *
258 * @return A {@code List} of supported services.
259 */
260 public List<BluetoothGattService> getSupportedGattServices() {
261 if (mBluetoothGatt == null){
262 return null;
263 }
264
265 return mBluetoothGatt.getServices();
266 }
267
268 /**
269 * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
270 * asynchronously through the
271 * {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
272 * callback.
273 *
274 * @param characteristic The characteristic to read from.
275 */
276 public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
277 if (mBluetoothAdapter == null || mBluetoothGatt == null) {
278 if (Util.WARN) Log.w(TAG, "Bluetooth not initialized");
279 return;
280 }
281
282 // Queue the characteristic to read
283 BleRequest req = new BleRequest();
284 req.characteristic = characteristic;
285 req.operation = BleRequestOperation.read;
286 characteristicQueue.add(req);
287
288 // If there is only 1 item in the queue, then read it. If more than 1, it is handled
289 // asynchronously in the callback
290 if((characteristicQueue.size() == 1)) {
291 mBluetoothGatt.readCharacteristic(characteristic);
292 }
293 }
294
295 /**
296 * Request a write on a given {@code BluetoothGattCharacteristic}. The write result is reported
297 * asynchronously through the
298 * {@code BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
299 * callback.
300 *
301 * @param characteristic The characteristic to write to.
302 */
303 public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
304 if (mBluetoothAdapter == null || mBluetoothGatt == null) {
305 if (Util.WARN) Log.w(TAG, "BluetoothAdapter not initialized");
306 return;
307 }
308 // Queue the characteristic to write
309 BleRequest req = new BleRequest();
310 req.characteristic = characteristic;
311 req.operation = BleRequestOperation.write;
312 characteristicQueue.add(req);
313
314 // If there is only 1 item in the queue, then write it. If more than 1, it is handled
315 // asynchronously in the callback
316 if((characteristicQueue.size() == 1)) {
317 mBluetoothGatt.writeCharacteristic(characteristic);
318 }
319 }
320
321 /**
322 * Request a write on a given {@code BluetoothGattCharacteristic} immediately. The write result is reported
323 * asynchronously through the
324 * {@code BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
325 * callback.
326 *
327 * @param characteristic The characteristic to write to.
328 */
329 public void writeCharacteristicNoResponse(BluetoothGattCharacteristic characteristic) {
330 if (mBluetoothAdapter == null || mBluetoothGatt == null) {
331 if (Util.WARN) Log.w(TAG, "BluetoothAdapter not initialized");
332 return;
333 }
334
335 mBluetoothGatt.writeCharacteristic(characteristic);
336 }
337
338 /**
339 * Scan for bluetooth devices
340 *
341 * @param enable Set to true to start scanning or to false to stop scanning
342 */
343 private void scanLeDevice(final boolean enable) {
344
345 if(mLEScanner == null) {
346 if (Util.WARN) Log.w(TAG, "Could not get LEScanner object");
347 return;
348 }
349
350 if (enable) {
351 // Clear list of scanned devices
352 mBtDevices.clear();
353 mScanning = true;
354 mLEScanner.startScan(mScanFilters, mScanSettings, mScanCallback);
355 } else {
356 mScanning = false;
357 mLEScanner.stopScan(mScanCallback);
358 }
359 }
360
361 /**
362 * Enable or disable notification on a given characteristic.
363 *
364 * @param characteristic Characteristic to act on.
365 * @param enable If true, enable notification. Otherwise, disable it.
366 */
367 public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
368 boolean enable) {
369 if (mBluetoothAdapter == null || mBluetoothGatt == null) {
370 if (Util.WARN) Log.w(TAG, "Bluetooth not initialized");
371 return;
372 }
373 // Enable/disable notification
374 boolean status = mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
375 if(status == false){
376 if (Util.WARN) Log.w(TAG, "Set notification failed");
377 return;
378 }
379
380 // Write descriptor for notification
381 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG);
382 if(descriptor != null)
383 {
384 descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : new byte[]{0x00, 0x00});
385 writeGattDescriptor(descriptor);
386 }
387 }
388
389 /**
390 * Set the connection priority.
391 *
392 * @param connectionPriority
393 * @return bool whether the call was successful
394 */
395 public boolean requestConnectionPriority(int connectionPriority) {
396 return this.mBluetoothGatt.requestConnectionPriority(connectionPriority);
397 }
398
399 /**
400 * Bluetooth gatt callback function
401 */
402 private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
403
404 @Override
405 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
406 if(Util.INFO) Log.i(TAG, "onConnectionStateChange. Status: " + status);
407 String intentAction;
408
409 switch (newState) {
410 case BluetoothProfile.STATE_CONNECTED:
411 intentAction = ACTION_GATT_CONNECTED;
412 broadcastUpdate(intentAction);
413 if(Util.INFO) Log.i(TAG, "STATE_CONNECTED");
414 gatt.discoverServices();
415 break;
416 case BluetoothProfile.STATE_DISCONNECTED:
417 if(Util.INFO) Log.i(TAG, "STATE_DISCONNECTED");
418 intentAction = ACTION_GATT_DISCONNECTED;
419 broadcastUpdate(intentAction);
420 // Close connection completely after disconnect, to be able
421 // to start clean.
422 if (mBluetoothGatt != null) {
423 mBluetoothGatt.close();
424 mBluetoothGatt = null;
425 }
426 break;
427 default:
428 if (Util.ERROR) Log.e(TAG, "STATE_OTHER");
429 }
430 }
431
432 @Override
433 public void onServicesDiscovered(BluetoothGatt gatt, int status) {
434 Log.i(TAG, "onServicesDiscovered: " + status);
435 if (status == BluetoothGatt.GATT_SUCCESS) {
436 // Broadcast that services has successfully been discovered
437 broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
438 } else {
439 if (Util.WARN) Log.w(TAG, "onServicesDiscovered received with error: " + status);
440 }
441 }
442
443 @Override
444 public void onCharacteristicChanged(BluetoothGatt gatt,
445 BluetoothGattCharacteristic characteristic) {
446 broadcastUpdate(ACTION_DATA_NOTIFY, characteristic);
447 }
448
449 @Override
450 public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
451 int status) {
452
453 // Read action has finished, remove from queue
454 characteristicQueue.remove();
455
456 // Broadcast the result
457 if (status == BluetoothGatt.GATT_SUCCESS) {
458 broadcastUpdate(ACTION_DATA_READ, characteristic);
459 }
460 else{
461 if (Util.ERROR) Log.e(TAG, "onCharacteristicRead error: " + status);
462 }
463
464 // Handle the next element from the queue
465 if(characteristicQueue.size() > 0){
466 BleRequest req = characteristicQueue.element();
467 switch(req.operation)
468 {
469 case write:
470 mBluetoothGatt.writeCharacteristic(req.characteristic);
471 break;
472 case read:
473 mBluetoothGatt.readCharacteristic(req.characteristic);
474 break;
475 case enableNotification:
476 mBluetoothGatt.writeDescriptor(req.descriptor);
477 break;
478 }
479 }
480 }
481
482 @Override
483 public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
484 if (status != BluetoothGatt.GATT_SUCCESS) {
485 if (Util.ERROR) Log.e(TAG, "Callback: Error writing GATT Descriptor: "+ status);
486 }
487
488 // Write finished, remove from queue
489 characteristicQueue.remove();
490
491 // Continue handling items if there is more in the queue
492 if(characteristicQueue.size() > 0){
493 BleRequest req = characteristicQueue.element();
494 switch(req.operation)
495 {
496 case write:
497 mBluetoothGatt.writeCharacteristic(req.characteristic);
498 break;
499 case read:
500 mBluetoothGatt.readCharacteristic(req.characteristic);
501 break;
502 case enableNotification:
503 mBluetoothGatt.writeDescriptor(req.descriptor);
504 break;
505 }
506 }
507
508 };
509
510 @Override
511 public void onCharacteristicWrite(BluetoothGatt gatt,
512 BluetoothGattCharacteristic characteristic, int status) {
513
514 if (Util.INFO) Log.i(TAG, "onCharacteristicWrite: "+ status);
515
516 // Broadcast the result
517 if (status == BluetoothGatt.GATT_SUCCESS) {
518 broadcastUpdate(ACTION_DATA_WRITE, characteristic);
519 }
520 else {
521 if (Util.WARN) Log.w(TAG, "Write failed");
522 }
523
524 // Handle the queue if used
525 if((characteristicQueue.size() > 0)) {
526
527 // Write action has finished, remove from queue
528 characteristicQueue.remove();
529
530 // Handle the next element from the queue
531 if (characteristicQueue.size() > 0) {
532 BleRequest req = characteristicQueue.element();
533 switch (req.operation) {
534 case write:
535 mBluetoothGatt.writeCharacteristic(req.characteristic);
536 break;
537 case read:
538 mBluetoothGatt.readCharacteristic(req.characteristic);
539 break;
540 case enableNotification:
541 mBluetoothGatt.writeDescriptor(req.descriptor);
542 break;
543 }
544 }
545 }
546 }
547 };
548
549 /**
550 * Device scan callback
551 */
552 private ScanCallback mScanCallback = new ScanCallback() {
553
554 @Override
555 public void onScanResult(int callbackType, ScanResult result) {
556
557 final BluetoothDevice btDevice = result.getDevice();
558 if (btDevice == null){
559 if (Util.ERROR) Log.e("ScanCallback", "Could not get bluetooth device");
560 return;
561 }
562
563 // Check if device already added to list of scanned devices
564 String macAddress = btDevice.getAddress();
565 for(BluetoothDevice dev : mBtDevices.keySet())
566 {
567 // Device already added, do nothing
568 if(dev.getAddress().equals(macAddress) ){
569 return;
570 }
571 }
572
573 // Add device to list of scanned devices
574 mBtDevices.put(btDevice, result.getRssi());
575
576 // Update the device table with the new device
577 updateDeviceTable();
578 }
579 };
580
581 /**
582 * Update the table view displaying all scanned devices.
583 * This function will clean the current table view and re-add all items that has been scanned
584 */
585 private void updateDeviceTable() {
586
587 // Clean current table view
588 mTableDevices.removeAllViews();
589
590 for(final BluetoothDevice savedDevice : mBtDevices.keySet()) {
591
592 // Get RSSI of this device
593 int rssi = mBtDevices.get(savedDevice);
594
595 // Create a new row
596 final TableRow tr = new TableRow(MainActivity.this);
597 tr.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT));
598
599 // Add Text view for rssi
600 TextView tvRssi = new TextView(MainActivity.this);
601 tvRssi.setText(Integer.toString(rssi));
602 TableRow.LayoutParams params = new TableRow.LayoutParams(0);
603 params.setMargins(20,0,20,0);
604 tvRssi.setLayoutParams(params);
605
606 // Add Text view for device, displaying name and address
607 TextView tvDevice = new TextView(MainActivity.this);
608 String deviceName = savedDevice.getName();
609 if(deviceName == null){
610 deviceName ="";
611 }
612 tvDevice.setText(deviceName + "\r\n" + savedDevice.getAddress());
613 tvDevice.setLayoutParams(new TableRow.LayoutParams(1));
614
615 // Add a connect button to the right
616 Button b = new Button(MainActivity.this);
617 b.setText(R.string.button_connect);
618 b.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
619
620 // Create action when clicking the connect button
621 b.setOnClickListener(new View.OnClickListener() {
622
623 public void onClick(View v) {
624
625 // Create the activity for the selected device
626 final Intent intent = new Intent(MainActivity.this, FwUpdateActivity.class);
627 intent.putExtra(FwUpdateActivity.EXTRAS_DEVICE_NAME, savedDevice.getName());
628
629 // Connect to device
630 connectToDevice(savedDevice);
631
632 // start activity
633 startActivity(intent);
634 }
635 });
636
637 // Add items to the row
638 tr.addView(tvRssi);
639 tr.addView(tvDevice);
640 tr.addView(b);
641 tr.setGravity(Gravity.CENTER);
642
643 // Add row to the table layout
644 MainActivity.this.runOnUiThread(new Runnable() {
645 @Override
646 public void run() {
647 mTableDevices.addView(tr);
648 }
649 });
650 }
651 }
652
653 /**
654 * Broadcast an update on the specified action
655 */
656 private void broadcastUpdate(final String action) {
657 final Intent intent = new Intent(action);
658 sendBroadcast(intent);
659 }
660
661 /**
662 * Broadcast an update on the specified action
663 */
664 private void broadcastUpdate(final String action,
665 final BluetoothGattCharacteristic characteristic) {
666
667 final Intent intent = new Intent(action);
668 intent.putExtra(EXTRA_UUID, characteristic.getUuid().toString());
669 intent.putExtra(EXTRA_DATA, characteristic.getValue());
670 sendBroadcast(intent);
671 }
672
673 /**
674 * Write gatt descriptor if queue is ready.
675 */
676 private void writeGattDescriptor(BluetoothGattDescriptor d){
677 // Add descriptor to the write queue
678 BleRequest req = new BleRequest();
679 req.descriptor = d;
680 req.operation = BleRequestOperation.enableNotification;
681 characteristicQueue.add(req);
682 // If there is only 1 item in the queue, then write it. If more than 1, it will be handled
683 // in the onDescriptorWrite callback
684 if(characteristicQueue.size() == 1){
685 mBluetoothGatt.writeDescriptor(d);
686 }
687 }
688
689
690}
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
index 0000000..0d9963c
--- /dev/null
+++ b/app/src/main/java/com/example/ti/oadexample/OadProcess.java
@@ -0,0 +1,474 @@
1package com.example.ti.oadexample;
2
3import android.app.AlertDialog;
4import android.bluetooth.BluetoothGattCharacteristic;
5import android.content.DialogInterface;
6import android.os.Handler;
7import android.util.Log;
8
9import java.io.File;
10import java.io.FileInputStream;
11import java.io.IOException;
12import java.io.InputStream;
13import java.util.Timer;
14import java.util.TimerTask;
15
16/**
17 * Class containing the OAD handling
18 */
19public class OadProcess
20{
21
22 private FwUpdateActivity mFwUpdateActivity = null;
23 private MainActivity mMainActivity = null;
24
25 // Tag used for logging
26 private static final String TAG = "OadProcess";
27
28 // Bluetooth
29 private BluetoothGattCharacteristic mCharIdentify = null;
30 private BluetoothGattCharacteristic mCharBlock = null;
31
32 // Programming
33 private static final short OAD_CONN_INTERVAL = 12; // units of 1.25ms = 15ms
34 private static final short OAD_SUPERVISION_TIMEOUT = 50; // units of 10 ms = 500 milliseconds
35 private static final int OAD_BLOCK_SIZE = 16;
36 private static final int OAD_BUFFER_SIZE = 2 + OAD_BLOCK_SIZE;
37 private static final int HAL_FLASH_WORD_SIZE = 4;
38 private static final long TIMER_INTERVAL = 1000;
39 public static final int FILE_BUFFER_SIZE = 0x40000;
40 private byte[] mFileBuffer = new byte[FILE_BUFFER_SIZE];
41 private ImageHeader mFileImgHdr;
42 private final byte[] mOadBuffer = new byte[OAD_BUFFER_SIZE];
43 private ProgramInfo mProgramInfo = new ProgramInfo();
44 private Timer mTimer = null;
45 private TimerTask mTimerTask = null;
46 private Handler mFastModeHandler;
47
48 // Housekeeping
49 private boolean mProgramming = false;
50 boolean mImageHasHeader = false;
51
52
53 /**
54 * Constructor
55 */
56 public static OadProcess newInstance( MainActivity mainActivity, FwUpdateActivity fwUpdateActivity) {
57 OadProcess oad = new OadProcess();
58 oad.mCharBlock = fwUpdateActivity.mCharBlock;
59 oad.mCharIdentify = fwUpdateActivity.mCharIdentify;
60 oad.mFwUpdateActivity = fwUpdateActivity;
61 oad.mMainActivity = mainActivity;
62 return oad;
63 }
64
65 /**
66 * Inner class for fw image header info
67 */
68 private class ImageHeader {
69 short crc0;
70 short crc1;
71 short version;
72 int length;
73 byte[] uid = new byte[4];
74 short address;
75 byte imgType;
76 byte state;
77
78 /**
79 * Constructor
80 * @param buffer buffer with image to program
81 * @param fileLen length of image to program
82 */
83 ImageHeader(byte[] buffer, int fileLen) {
84
85 // Check if image header exists in file
86 if(fileLen > 15){
87 int i = (mFileBuffer[11] << 24) | (mFileBuffer[10]<< 16) |
88 (mFileBuffer[9] << 8) | mFileBuffer[8];
89 if (i== 0x45454545){
90 // Header exist, read it
91 mImageHasHeader = true;
92 this.length = Util.buildUint16(mFileBuffer[7], mFileBuffer[6]);
93 this.version = Util.buildUint16(mFileBuffer[5], mFileBuffer[4]);
94 this.uid[0] = this.uid[1] = this.uid[2] = this.uid[3] = 'E';
95 this.address = Util.buildUint16(mFileBuffer[13], mFileBuffer[12]);
96 this.imgType = mFileBuffer[14];
97 this.state = mFileBuffer[15];
98 this.crc0 = Util.buildUint16(mFileBuffer[1], mFileBuffer[0]);
99 crc1 = Util.buildUint16(mFileBuffer[3], mFileBuffer[2]);
100 if (Util.DEBUG) {
101 Log.d(TAG, "Read Header");
102 Log.d(TAG, "ImgHdr.length = " + this.length);
103 Log.d(TAG, "ImgHdr.version = " + this.version);
104 Log.d(TAG, String.format("ImgHdr.uid = %02x%02x%02x%02x", this.uid[0], this.uid[1], this.uid[2], this.uid[3]));
105 Log.d(TAG, "ImgHdr.address = " + this.address);
106 Log.d(TAG, "ImgHdr.imgType = " + this.imgType);
107 Log.d(TAG, String.format("ImgHdr.crc0 = %04x", this.crc0));
108 }
109
110 return;
111 }
112 }
113
114 // Header not found in file, create one
115 this.length = (fileLen / 4);
116 this.version = 0;
117 this.uid[0] = this.uid[1] = this.uid[2] = this.uid[3] = 'E';
118 this.address = 0;
119 this.imgType = 1; //EFL_OAD_IMG_TYPE_APP
120 this.crc0 = calcImageCRC((int)0,buffer);
121 crc1 = (short)0xFFFF;
122 this.state = (byte)0xFF;
123 if (Util.DEBUG) {
124 Log.d(TAG, "Generated Header");
125 Log.d(TAG, "ImgHdr.length = " + this.length);
126 Log.d(TAG, "ImgHdr.version = " + this.version);
127 Log.d(TAG, String.format("ImgHdr.uid = %02x%02x%02x%02x", this.uid[0], this.uid[1], this.uid[2], this.uid[3]));
128 Log.d(TAG, "ImgHdr.address = " + this.address);
129 Log.d(TAG, "ImgHdr.imgType = " + this.imgType);
130 Log.d(TAG, String.format("ImgHdr.crc0 = %04x", this.crc0));
131 }
132 }
133
134 /**
135 * Function returning a byte array with image header
136 */
137 byte[] getRequest() {
138 byte[] tmp = new byte[16];
139 tmp[0] = Util.loUint16(this.crc0);
140 tmp[1] = Util.hiUint16(this.crc0);
141 tmp[2] = Util.loUint16(this.crc1);
142 tmp[3] = Util.hiUint16(this.crc1);
143 tmp[4] = Util.loUint16(this.version);
144 tmp[5] = Util.hiUint16(this.version);
145 tmp[6] = Util.loUint16((short)this.length);
146 tmp[7] = Util.hiUint16((short)this.length);
147 tmp[8] = tmp[9] = tmp[10] = tmp[11] = this.uid[0];
148 tmp[12] = Util.loUint16(this.address);
149 tmp[13] = Util.hiUint16(this.address);
150 tmp[14] = imgType;
151 tmp[15] = (byte) 0xFF;
152 return tmp;
153 }
154
155 /**
156 * Calculate the CRC of image to program
157 */
158 short calcImageCRC(int page, byte[] buf) {
159 short crc = 0;
160 long addr = page * 0x1000;
161
162 byte pageBeg = (byte)page;
163 byte pageEnd = (byte)(this.length / (0x1000 / 4));
164 int osetEnd = ((this.length - (pageEnd * (0x1000 / 4))) * 4);
165
166 pageEnd += pageBeg;
167
168 while (true) {
169 int oset;
170
171 for (oset = 0; oset < 0x1000; oset++) {
172 if ((page == pageBeg) && (oset == 0x00)) {
173 // Skip the CRC and shadow.
174 // Note: this increments by 3 because oset is incremented by 1 in each pass
175 // through the loop
176 oset += 3;
177 }
178 else if ((page == pageEnd) && (oset == osetEnd)) {
179 crc = this.crc16(crc,(byte)0x00);
180 crc = this.crc16(crc,(byte)0x00);
181
182 return crc;
183 }
184 else {
185 crc = this.crc16(crc,buf[(int)(addr + oset)]);
186 }
187 }
188 page += 1;
189 addr = page * 0x1000;
190 }
191 }
192
193 short crc16(short crc, byte val) {
194 final int poly = 0x1021;
195 byte cnt;
196 for (cnt = 0; cnt < 8; cnt++, val <<= 1) {
197 byte msb;
198 if ((crc & 0x8000) == 0x8000) {
199 msb = 1;
200 }
201 else msb = 0;
202
203 crc <<= 1;
204 if ((val & 0x80) == 0x80) {
205 crc |= 0x0001;
206 }
207 if (msb == 1) {
208 crc ^= poly;
209 }
210 }
211
212 return crc;
213 }
214 }
215
216 /**
217 * Inner class for the programming timer
218 */
219 private class ProgramTimerTask extends TimerTask {
220 @Override
221 public void run() {
222 mProgramInfo.iTimeElapsed += TIMER_INTERVAL;
223 }
224 }
225
226 /**
227 * Inner class keeping programming status
228 */
229 private class ProgramInfo {
230 int iBytes = 0; // Number of bytes programmed
231 short iBlocks = 0; // Number of blocks programmed
232 short nBlocks = 0; // Total number of blocks
233 int iTimeElapsed = 0; // Time elapsed in milliseconds
234
235 void reset() {
236 iBytes = 0;
237 iBlocks = 0;
238 iTimeElapsed = 0;
239 nBlocks = (short) (mFileImgHdr.length / (OAD_BLOCK_SIZE / HAL_FLASH_WORD_SIZE));
240 }
241 }
242
243 /**
244 * Display the programming progress
245 */
246 private void displayProgress() {
247 String txt;
248 int byteRate;
249 int sec = mProgramInfo.iTimeElapsed / 1000;
250 if (sec > 0) {
251 byteRate = mProgramInfo.iBytes / sec;
252 } else {
253 return;
254 }
255 float timeEstimate = ((float)(mFileImgHdr.length *4) / (float)mProgramInfo.iBytes) * sec;
256
257 txt = String.format("Time: %d / %d sec", sec, (int)timeEstimate);
258 txt += String.format(" Bytes: %d (%d/sec)", mProgramInfo.iBytes, byteRate);
259 mFwUpdateActivity.displayProgressText(txt);
260 }
261
262 /**
263 * Check if programming is in action
264 */
265 public boolean isProgramming()
266 {
267 return mProgramming;
268 }
269
270 /**
271 * Called when the user has chosen a file
272 */
273 public int readFile(String filepath) {
274 int readLen = 0;
275
276 // Load binary file
277 try {
278 // Read the file raw into a buffer
279 InputStream stream;
280 File f = new File(filepath);
281 stream = new FileInputStream(f);
282 readLen = stream.read(mFileBuffer, 0, mFileBuffer.length);
283 stream.close();
284 } catch (IOException e) {
285 // Handle exceptions here
286 mFwUpdateActivity.log("File open failed: " + filepath + "\n", false);
287 return -1;
288 }
289
290 // Pad image with 0xFF to align with 16 bytes
291 if((readLen % 16) != 0){
292 Log.d(TAG, "length = " + mFileBuffer.length);
293 while((readLen % 16) != 0){
294 mFileBuffer[readLen] = (byte)0xFF;
295 readLen++;
296 }
297 }
298
299 // Create image header
300 mFileImgHdr = new ImageHeader(mFileBuffer,readLen);
301 if (mImageHasHeader && (mFileImgHdr.state == (byte)0xFE))
302 {
303 // Image header is written in the file, do not include it in the programming
304 System.arraycopy(mFileBuffer, 16,mFileBuffer, 0, mFileBuffer.length - 16);
305 readLen -= 16;
306 }
307
308 return readLen;
309 }
310
311 /**
312 * Program one block of bytes.
313 * In safe mode, this function is called when a notification with the current image info
314 * has been received. In unsafe mode, it is called repeatedly with a delay.
315 */
316 public void programBlock() {
317 if (!mProgramming)
318 return;
319
320 if (mProgramInfo.iBlocks < mProgramInfo.nBlocks)
321 {
322 mProgramming = true;
323
324 // Prepare block
325 mOadBuffer[0] = Util.loUint16(mProgramInfo.iBlocks);
326 mOadBuffer[1] = Util.hiUint16(mProgramInfo.iBlocks);
327 System.arraycopy(mFileBuffer, mProgramInfo.iBytes, mOadBuffer, 2, OAD_BLOCK_SIZE);
328
329 // Send block
330 mCharBlock.setValue(mOadBuffer);
331 mMainActivity.writeCharacteristicNoResponse(mCharBlock);
332 String block = String.format("%02x%02x",mOadBuffer[1],mOadBuffer[0]);
333 if (Util.DEBUG) Log.d(TAG,"Sent block :" + block /*mProgramInfo.iBlocks*/);
334
335 // Update statistics
336 mProgramInfo.iBlocks++;
337 mProgramInfo.iBytes += OAD_BLOCK_SIZE;
338 mFwUpdateActivity.updateProgressBar((mProgramInfo.iBlocks * 100) / mProgramInfo.nBlocks);
339 if (mProgramInfo.iBlocks == mProgramInfo.nBlocks) {
340
341 // Programming has finished
342 AlertDialog.Builder b = new AlertDialog.Builder(mFwUpdateActivity);
343 b.setMessage(R.string.oad_dialog_programming_finished);
344 b.setTitle("Programming finished");
345 b.setPositiveButton("OK", new DialogInterface.OnClickListener() {
346 @Override
347 public void onClick(DialogInterface dialogInterface, int i) {
348 mFwUpdateActivity.finish();
349 }
350 });
351
352 AlertDialog d = b.create();
353 d.show();
354 mProgramming = false;
355 mFwUpdateActivity.log(("Programming finished at block " + (mProgramInfo.iBlocks + 1) + "\n"), true);
356 }
357
358 }
359 else
360 {
361 mProgramming = false;
362 }
363
364 if ((mProgramInfo.iBlocks % 100) == 0) {
365 // Display statistics each 100th block
366 mFwUpdateActivity.runOnUiThread(new Runnable() {
367 public void run() {
368 displayProgress();
369 }
370 });
371 }
372
373 if (!mProgramming)
374 {
375 mFwUpdateActivity.runOnUiThread(new Runnable() {
376 public void run() {
377 displayProgress();
378 stopProgramming();
379 }
380 });
381 }
382 }
383
384 /**
385 * Start programming image
386 */
387 public void startProgramming() {
388
389 // Enable notifications on characteristics
390 mMainActivity.setCharacteristicNotification(mCharIdentify,true);
391 mMainActivity.setCharacteristicNotification(mCharBlock,true);
392 BluetoothGattCharacteristic imageStatusChar = mFwUpdateActivity.mCharImageStatus;
393 mMainActivity.setCharacteristicNotification(imageStatusChar,true);
394
395 // Send image header
396 mCharIdentify.setValue(mFileImgHdr.getRequest());
397 mMainActivity.writeCharacteristic(mCharIdentify);
398
399 // Update GUI
400 mFwUpdateActivity.log("Programming started\n", true);
401 mProgramming = true;
402 mFwUpdateActivity.updateGui(mProgramming);
403
404 // Initialize statistics
405 mProgramInfo.reset();
406 mTimer = new Timer();
407 mTimerTask = new ProgramTimerTask();
408 mTimer.scheduleAtFixedRate(mTimerTask, 0, TIMER_INTERVAL);
409
410 if(!mFwUpdateActivity.mSafeMode){
411 // Fast mode. Start runnable that program blocks with a delay
412 mFastModeHandler = new Handler();
413 mFastModeHandler.postDelayed(r, 150);
414 }
415 }
416
417 /**
418 * Runnable used for fast programming mode. Blocks of bytes
419 * are programmed continuously with a delay
420 */
421 private Runnable r = new Runnable() {
422
423 @Override
424 public void run() {
425 if(mProgramming){
426 // Program block and delay
427 programBlock();
428 mFastModeHandler.postDelayed(this, mFwUpdateActivity.mBlockDelay);
429 }
430 else{
431 // Stop runnable
432 mFastModeHandler.removeCallbacks(this);
433 }
434 }
435 };
436
437 /**
438 * Stop programming of image
439 */
440 public void stopProgramming() {
441 mTimer.cancel();
442 mTimer.purge();
443 mTimerTask.cancel();
444 mTimerTask = null;
445
446 mProgramming = false;
447 mFwUpdateActivity.displayProgressText("");
448 mFwUpdateActivity.updateProgressBar(0);
449 mFwUpdateActivity.updateGui(mProgramming);
450
451 if (mProgramInfo.iBlocks == mProgramInfo.nBlocks) {
452 mFwUpdateActivity.log("Programming complete!\n", false);
453 } else {
454 mFwUpdateActivity.log("Programming cancelled\n", true);
455 }
456
457 // Disable notification on characteristics
458 mMainActivity.setCharacteristicNotification(mCharBlock, false);
459 }
460
461 /**
462 * Function trying to set the BLE connection parameters
463 */
464 public void setConnectionParameters() {
465 // Make sure connection interval is long enough for OAD
466 byte[] value = {Util.loUint16(OAD_CONN_INTERVAL), Util.hiUint16(OAD_CONN_INTERVAL), Util.loUint16(OAD_CONN_INTERVAL),
467 Util.hiUint16(OAD_CONN_INTERVAL), 0, 0, Util.loUint16(OAD_SUPERVISION_TIMEOUT), Util.hiUint16(OAD_SUPERVISION_TIMEOUT) };
468
469 BluetoothGattCharacteristic charConnReq = mFwUpdateActivity.getCharConnReq();
470 charConnReq.setValue(value);
471 mMainActivity.writeCharacteristic(charConnReq);
472 }
473
474}
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
index 0000000..490cbf2
--- /dev/null
+++ b/app/src/main/java/com/example/ti/oadexample/TIOADProfile.java
@@ -0,0 +1,87 @@
1package com.example.ti.oadexample;
2
3import android.bluetooth.BluetoothGattCharacteristic;
4import android.bluetooth.BluetoothGattService;
5import java.util.UUID;
6
7
8public class TIOADProfile
9{
10 // Test service
11 public static final String TEST_SERVICE = "f000aa64-0451-4000-b000-000000000000";
12 public static final String TEST_DATA_CHAR = "f000aa65-0451-4000-b000-000000000000"; // Test result
13
14 // OAD service
15 public static final String OAD_SERVICE = "f000ffc0-0451-4000-b000-000000000000";
16 public static final String OAD_IMAGE_NOTIFY_CHAR = "f000ffc1-0451-4000-b000-000000000000";
17 public static final String OAD_BLOCK_REQUEST_CHAR = "f000ffc2-0451-4000-b000-000000000000";
18 public static final String OAD_IMAGE_STATUS_CHAR = "f000ffc4-0451-4000-b000-000000000000";
19
20 // Connection control service
21 public static final String CONNECTION_CONTROL_SERVICE = "f000ccc0-0451-4000-b000-000000000000";
22 public static final String REQUEST_CONNECTION_PARAMETERS_CHAR = "f000ccc2-0451-4000-b000-000000000000";
23
24 /**
25 * Verify that the given service is a TIOAD service
26 */
27 public static boolean isOadService(BluetoothGattService service)
28 {
29 if ((service.getUuid().toString().compareTo(OAD_SERVICE)) == 0)
30 {
31 // Verify the correct characteristics
32 BluetoothGattCharacteristic c = service.getCharacteristic(UUID.fromString(OAD_IMAGE_NOTIFY_CHAR));
33 if(c == null)
34 {
35 return false;
36 }
37 c = service.getCharacteristic(UUID.fromString(OAD_BLOCK_REQUEST_CHAR));
38 if(c == null)
39 {
40 return false;
41 }
42 c = service.getCharacteristic(UUID.fromString(OAD_IMAGE_STATUS_CHAR));
43 if(c == null)
44 {
45 return false;
46 }
47 return true;
48 }
49 return false;
50 }
51
52 /**
53 * Verify that the given service is the Test service
54 */
55 public static boolean isTestService(BluetoothGattService service) {
56
57 if ((service.getUuid().toString().compareTo(TEST_SERVICE)) == 0)
58 {
59 // Verify the correct characteristics
60 BluetoothGattCharacteristic c = service.getCharacteristic(UUID.fromString(TEST_DATA_CHAR));
61 if(c == null)
62 {
63 return false;
64 }
65 return true;
66 }
67 return false;
68 }
69
70 /**
71 * Verify that the given service is the Connection Control service
72 */
73 public static boolean isConnectionControlService(BluetoothGattService service) {
74 if ((service.getUuid().toString().compareTo(CONNECTION_CONTROL_SERVICE)) == 0)
75 {
76 // Verify the correct characteristics
77 BluetoothGattCharacteristic c = service.getCharacteristic(UUID.fromString(REQUEST_CONNECTION_PARAMETERS_CHAR));
78 if(c == null)
79 {
80 return false;
81 }
82 return true;
83 }
84 return false;
85 }
86
87}
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
index 0000000..4c98936
--- /dev/null
+++ b/app/src/main/java/com/example/ti/oadexample/Util.java
@@ -0,0 +1,35 @@
1package com.example.ti.oadexample;
2
3/**
4 * Utility class
5 */
6public class Util
7{
8 // Choose loglevel
9 public static int LOGLEVEL = 1;
10 public static boolean ERROR = LOGLEVEL > 0;
11 public static boolean WARN = LOGLEVEL > 1;
12 public static boolean INFO = LOGLEVEL > 2;
13 public static boolean DEBUG = LOGLEVEL > 3;
14
15 /**
16 * Get lower byte of an uint16
17 */
18 public static byte loUint16(short v) {
19 return (byte) (v & 0xFF);
20 }
21
22 /**
23 * Get high byte of an uint16
24 */
25 public static byte hiUint16(short v) {
26 return (byte) (v >> 8);
27 }
28
29 /**
30 * Build a uint16 from two bytes
31 */
32 public static short buildUint16(byte hi, byte lo) {
33 return (short) ((hi << 8) + (lo & 0xff));
34 }
35}
diff --git a/app/src/main/res/drawable/gradient_bg.xml b/app/src/main/res/drawable/gradient_bg.xml
new file mode 100644
index 0000000..13b67fe
--- /dev/null
+++ b/app/src/main/res/drawable/gradient_bg.xml
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="utf-8"?>
2<shape xmlns:android="http://schemas.android.com/apk/res/android"
3 android:shape="rectangle">
4 <gradient
5 android:startColor="#f1f1f2"
6 android:centerColor="#e7e7e8"
7 android:endColor="#cfcfcf"
8 android:angle="270" />
9</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
index 0000000..ed87256
--- /dev/null
+++ b/app/src/main/res/drawable/gradient_pressed_bg.xml
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?>
2<shape xmlns:android="http://schemas.android.com/apk/res/android"
3 android:shape="rectangle">
4 <gradient
5 android:startColor="#4d5ec1"
6 android:endColor="#7380ce"
7 android:angle="270" />
8</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
index 0000000..a189052
--- /dev/null
+++ b/app/src/main/res/drawable/group_box.xml
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="utf-8"?>
2<shape xmlns:android="http://schemas.android.com/apk/res/android">
3 <solid android:color="#ffffff" />
4 <stroke android:width="4dp" android:color="#ffffff" />
5 <padding android:left="7dp" android:top="7dp"
6 android:right="7dp" android:bottom="7dp" />
7 <corners android:radius="4dp" />
8
9</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
index 0000000..69b1519
--- /dev/null
+++ b/app/src/main/res/drawable/list_border.xml
@@ -0,0 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?>
2<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
3 <stroke android:width="1dp" android:color="#b5b5b5" />
4 <solid android:color="#00000000" />
5</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
index 0000000..e97864c
--- /dev/null
+++ b/app/src/main/res/drawable/states_selector_list.xml
@@ -0,0 +1,4 @@
1<?xml version="1.0" encoding="utf-8"?>
2<selector xmlns:android="http://schemas.android.com/apk/res/android">
3 <item android:drawable="@drawable/gradient_bg"/>
4</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
index 0000000..ac26db7
--- /dev/null
+++ b/app/src/main/res/layout/activity_file.xml
@@ -0,0 +1,18 @@
1<?xml version="1.0" encoding="utf-8"?>
2<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 android:fitsSystemWindows="true"
7 tools:context="com.example.ti.oadexample.FileActivity">
8
9 <android.support.design.widget.AppBarLayout
10 android:layout_width="match_parent"
11 android:layout_height="wrap_content"
12 android:theme="@style/AppTheme.AppBarOverlay">
13
14 </android.support.design.widget.AppBarLayout>
15
16 <include layout="@layout/content_file" />
17
18</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
index 0000000..a475464
--- /dev/null
+++ b/app/src/main/res/layout/activity_fw_update.xml
@@ -0,0 +1,25 @@
1<?xml version="1.0" encoding="utf-8"?>
2<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:fitsSystemWindows="true"
8 tools:context="com.example.ti.oadexample.FwUpdateActivity">
9
10 <android.support.design.widget.AppBarLayout
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 android:theme="@style/AppTheme.AppBarOverlay">
14
15 <android.support.v7.widget.Toolbar
16 android:id="@+id/toolbar"
17 android:layout_width="match_parent"
18 android:layout_height="?attr/actionBarSize"
19 android:background="?attr/colorPrimary"
20 app:popupTheme="@style/AppTheme.PopupOverlay" />
21
22 </android.support.design.widget.AppBarLayout>
23
24 <include layout="@layout/content_fw_update" />
25</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
index 0000000..5f61dc5
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,25 @@
1<?xml version="1.0" encoding="utf-8"?>
2<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:fitsSystemWindows="true"
8 tools:context="com.example.ti.oadexample.MainActivity">
9
10 <android.support.design.widget.AppBarLayout
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 android:theme="@style/AppTheme.AppBarOverlay">
14
15 <android.support.v7.widget.Toolbar
16 android:id="@+id/toolbar"
17 android:layout_width="match_parent"
18 android:layout_height="?attr/actionBarSize"
19 android:background="?attr/colorPrimary"
20 app:popupTheme="@style/AppTheme.PopupOverlay" />
21
22 </android.support.design.widget.AppBarLayout>
23
24 <include layout="@layout/content_main" />
25</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
index 0000000..cf10995
--- /dev/null
+++ b/app/src/main/res/layout/content_file.xml
@@ -0,0 +1,63 @@
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent"
5 android:layout_height="wrap_content"
6 android:orientation="vertical"
7 android:padding="10dp"
8 tools:context="com.example.ti.oadexample.FileActivity"
9 tools:showIn="@layout/activity_file"
10 android:focusableInTouchMode="true"
11 >
12
13 <TextView android:id="@+id/lbl_directory"
14 android:text="@string/label_directory"
15 android:layout_marginTop="10dp"
16 android:gravity="center_horizontal"
17 android:layout_width="fill_parent"
18 android:layout_height="wrap_content"/>
19
20 <EditText
21 android:id="@+id/et_directory"
22 android:text="download"
23 android:textColor="#003"
24 android:gravity="center_horizontal"
25 android:layout_width="fill_parent"
26 android:layout_height="wrap_content"
27 android:layout_below="@id/lbl_directory"
28 android:singleLine="true"/>
29 <ImageButton android:id="@+id/data_write"
30 android:layout_width="wrap_content"
31 android:layout_height="wrap_content"
32 android:onClick="onDirChanged"
33 android:background="@null"
34 android:src="@drawable/ic_action_refresh"
35 android:layout_alignTop="@id/et_directory"
36 android:layout_alignBottom="@id/et_directory"
37 android:layout_alignRight="@id/et_directory" />
38
39 <ListView
40 android:id="@+id/lv_file"
41 android:layout_marginTop="10dp"
42 android:layout_width="fill_parent"
43 android:layout_height="wrap_content"
44 android:layout_above="@+id/btn_confirm"
45 android:layout_below="@id/et_directory"
46 android:choiceMode="singleChoice"
47 android:padding="1dp"
48 android:divider="#b5b5b5"
49 android:dividerHeight="1dp"
50 android:headerDividersEnabled="true"
51 android:background="@drawable/list_border"
52 android:listSelector="@drawable/states_selector_list"
53 />
54
55 <Button
56 android:id="@+id/btn_confirm"
57 android:layout_width="match_parent"
58 android:layout_height="wrap_content"
59 android:onClick="onConfirm"
60 android:text="@string/btn_txt_confirm"
61 android:layout_alignParentBottom="true"/>
62
63</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
index 0000000..14ae945
--- /dev/null
+++ b/app/src/main/res/layout/content_fw_update.xml
@@ -0,0 +1,248 @@
1<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent"
8 android:orientation="vertical"
9 android:background="@color/colorFwUpdateBkg"
10 app:layout_behavior="@string/appbar_scrolling_view_behavior"
11 tools:context="com.example.ti.oadexample.FwUpdateActivity"
12 tools:showIn="@layout/activity_fw_update"
13 >
14 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
15 xmlns:app="http://schemas.android.com/apk/res-auto"
16 xmlns:tools="http://schemas.android.com/tools"
17 android:layout_width="wrap_content"
18 android:layout_height="wrap_content"
19 android:orientation="vertical"
20 >
21
22 <LinearLayout
23 xmlns:android="http://schemas.android.com/apk/res/android"
24 android:layout_width="wrap_content"
25 android:layout_height="wrap_content"
26 android:orientation="vertical"
27 >
28
29 <!-- connection status -->
30 <LinearLayout
31 android:layout_marginLeft="10dp"
32 android:orientation="horizontal"
33 android:layout_width="match_parent"
34 android:layout_height="wrap_content"
35 android:gravity="left"
36 android:paddingTop="10dp"
37 android:paddingBottom="0dp"
38 android:paddingLeft="6dp"
39 android:paddingRight="6dp">
40 <TextView
41 style="@style/labelStyle"
42 android:layout_gravity="left"
43 android:layout_width="wrap_content"
44 android:layout_height="match_parent"
45 android:text="@string/label_state"
46 android:paddingRight="10dp"/>
47
48 <TextView android:id="@+id/connection_state"
49 style="@style/dataStyle"
50 android:layout_width="match_parent"
51 android:layout_height="match_parent"
52 android:text="@string/disconnected" />
53 </LinearLayout>
54
55 <!-- Image to program -->
56 <RelativeLayout
57 android:layout_margin="10dp"
58 android:background="@drawable/group_box"
59 android:orientation="vertical"
60 android:layout_width="match_parent"
61 android:layout_height="wrap_content"
62 android:paddingBottom="10dp"
63 android:paddingTop="1dp"
64 >
65
66 <!-- Header -->
67 <TextView android:id="@+id/tv_image_label"
68 style="@style/labelStyle"
69 android:layout_width="wrap_content"
70 android:layout_height="wrap_content"
71 android:text="@string/label_new_image"
72 android:gravity="center_vertical"
73 android:layout_alignParentTop="true"
74 android:paddingBottom="10dp"/>
75 <RelativeLayout
76 android:orientation="horizontal"
77 android:layout_width="match_parent"
78 android:layout_height="wrap_content"
79 android:layout_centerVertical="true"
80 android:layout_below="@id/tv_image_label">
81
82 <!-- Selected image -->
83 <TextView android:id="@+id/tv_new_image"
84 android:layout_width="wrap_content"
85 android:layout_height="wrap_content"
86 android:text=""
87 android:layout_toLeftOf="@+id/btn_selectImage"
88 android:layout_alignParentStart="true"
89 android:layout_centerVertical="true"/>
90
91 <!-- Select image button -->
92 <Button android:id="@+id/btn_selectImage"
93 android:layout_width="wrap_content"
94 android:layout_height="wrap_content"
95 android:text="@string/select_image"
96 android:onClick="onSelectImage"
97 android:layout_alignParentEnd="true"
98 android:layout_centerVertical="true"/>
99
100 </RelativeLayout>
101 </RelativeLayout>
102
103 <!-- Logging -->
104 <RelativeLayout
105 android:layout_margin="10dp"
106 android:background="@drawable/group_box"
107 android:orientation="vertical"
108 android:layout_width="match_parent"
109 android:layout_height="wrap_content"
110 android:paddingBottom="10dp"
111 android:paddingTop="1dp" >
112 <TextView
113 android:id="@+id/tv_label_log"
114 style="@style/labelStyle"
115 android:layout_width="fill_parent"
116 android:layout_height="wrap_content"
117 android:layout_gravity="left"
118 android:text="@string/label_log"
119 android:layout_alignParentTop="true"
120 android:paddingBottom="10dp"/>
121 <TextView
122 android:id="@+id/tv_log"
123 android:layout_width="match_parent"
124 android:layout_height="wrap_content"
125 android:padding="5dp"
126 android:scrollbars="vertical"
127 android:maxLines="10"
128 android:gravity="bottom"
129 android:layout_below="@id/tv_label_log"/>
130
131 </RelativeLayout>
132
133
134 <!-- Programming stuff -->
135 <LinearLayout
136 android:background="@drawable/group_box"
137 android:layout_margin="10dp"
138 android:orientation="vertical"
139 android:layout_width="match_parent"
140 android:layout_height="wrap_content"
141 android:paddingBottom="10dp"
142 android:paddingTop="1dp"
143 >
144
145 <!-- Header -->
146 <TextView
147 style="@style/labelStyle"
148 android:layout_width="fill_parent"
149 android:layout_height="wrap_content"
150 android:layout_gravity="left"
151 android:paddingBottom="10dp"
152 android:text="@string/label_program"/>
153
154 <!-- Safe mode -->
155 <CheckBox
156 android:layout_width="wrap_content"
157 android:layout_height="wrap_content"
158 android:text="@string/safe_mode"
159 android:id="@+id/cbSafeMode"
160 android:paddingBottom="5dp"
161 android:onClick="onSafeMode"
162 android:textColor="#8A000000"/>
163
164
165 <!-- Block delay -->
166 <LinearLayout
167 android:orientation="horizontal"
168 android:layout_width="match_parent"
169 android:layout_height="wrap_content"
170 android:gravity="left"
171 android:paddingTop="10dp"
172 android:paddingBottom="10dp"
173 android:paddingLeft="0dp"
174 android:paddingRight="6dp">
175
176 <!-- Header-->
177 <TextView
178 android:id="@+id/labelDelay"
179 android:layout_width="wrap_content"
180 android:layout_height="wrap_content"
181 android:paddingLeft="6dp"
182 android:text="@string/label_block_delay"
183 android:layout_below="@+id/cbSafeMode"
184 android:layout_alignParentEnd="true"
185 />
186
187 <!-- Slider -->
188 <SeekBar
189 android:id="@+id/sbDelay"
190 android:layout_width="0dp"
191 android:layout_height="wrap_content"
192 android:layout_weight="3"
193 android:gravity="center_vertical"
194 android:max="50"
195 android:progress="20"
196 />
197
198 <!-- Value -->
199 <TextView
200 android:id="@+id/tvDelay"
201 android:layout_width="0dp"
202 android:layout_height="wrap_content"
203 android:layout_weight="2"
204 android:paddingLeft="6dp"
205 android:layout_alignParentEnd="true"
206 />
207
208 </LinearLayout>
209
210
211 <!-- Line with progress status -->
212 <TextView
213 android:id="@+id/tv_info"
214 android:layout_width="fill_parent"
215 android:layout_height="wrap_content"
216 android:layout_gravity="center"
217 android:layout_marginBottom="5dp"
218 android:layout_weight="0"
219 android:gravity="center_horizontal"
220 android:text=""
221 android:textSize="14sp" />
222
223 <!-- Progress bar -->
224 <ProgressBar
225 android:id="@+id/pb_progress"
226 style="@android:style/Widget.Holo.Light.SeekBar"
227 android:layout_width="match_parent"
228 android:layout_height="wrap_content"
229 android:layout_marginBottom="5dp"
230 android:indeterminate="false"
231 android:max="100"
232 android:maxHeight="15dp"
233 android:minHeight="15dp"
234 android:progress="0" />
235
236 <!-- Program button -->
237 <Button
238 android:id="@+id/btn_program"
239 android:layout_width="match_parent"
240 android:layout_height="wrap_content"
241 android:onClick="onProgramImage"
242 android:text="@string/start_prog" />
243
244 </LinearLayout>
245
246 </LinearLayout>
247 </ScrollView>
248</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
index 0000000..356cfce
--- /dev/null
+++ b/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,20 @@
1<?xml version="1.0" encoding="utf-8"?>
2<ScrollView
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:layout_marginTop="?android:attr/actionBarSize"
6 android:layout_width="fill_parent"
7 android:layout_height="fill_parent"
8 android:orientation="vertical"
9 android:scrollbars="none"
10 android:layout_weight="1">
11 <TableLayout
12 android:id="@+id/devicesFound"
13 android:layout_width="match_parent"
14 android:layout_height="wrap_content"
15 android:stretchColumns="1"
16 android:orientation="vertical"
17 app:layout_behavior="@string/appbar_scrolling_view_behavior">
18 </TableLayout>
19</ScrollView>
20
diff --git a/app/src/main/res/layout/element_file.xml b/app/src/main/res/layout/element_file.xml
new file mode 100644
index 0000000..900b40a
--- /dev/null
+++ b/app/src/main/res/layout/element_file.xml
@@ -0,0 +1,14 @@
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:layout_width="fill_parent"
5 android:layout_height="fill_parent"
6 android:padding="18dp">
7 <TextView
8 android:id="@+id/name"
9 android:layout_width="fill_parent"
10 android:layout_height="wrap_content"
11 android:layout_alignParentTop="true"
12 android:gravity="left"/>
13
14</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
index 0000000..cde69bc
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files 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
index 0000000..c133a0c
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files 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
index 0000000..bfa42f0
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files 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
index 0000000..324e72c
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files 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
index 0000000..aee44e1
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..0a1f325
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3 <color name="colorPrimary">#3F51B5</color>
4 <color name="colorPrimaryDark">#303f9f</color>
5 <color name="colorAccent">#FF4081</color>
6 <color name="primaryLight">#553f51b5</color>
7 <color name="colorFwUpdateBkg">#e1e6e9</color>
8 <color name="colorWhite">#ffffff</color>
9</resources>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..812cb7b
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
1<resources>
2 <!-- Default screen margins, per the Android Design guidelines. -->
3 <dimen name="activity_horizontal_margin">16dp</dimen>
4 <dimen name="activity_vertical_margin">16dp</dimen>
5 <dimen name="fab_margin">16dp</dimen>
6</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..f210418
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,43 @@
1<resources>
2 <!-- Main activity -->
3 <string name="app_name">Over-the-Air Download</string>
4 <string name="button_connect">Connect</string>
5
6 <!-- FW Update activity -->
7 <string name="title_activity_fw_update">FwUpdateActivity</string>
8
9 <string name="label_state">State:</string>
10 <string name="disconnected">Disconnected</string>
11 <string name="connected">Connected</string>
12
13 <string name="label_new_image">Image to Program</string>
14 <string name="select_image">Select image...</string>
15
16 <string name="label_program">OAD</string>
17 <string name="safe_mode">Safe mode </string>
18 <string name="label_block_delay">Block delay:</string>
19 <string name="start_prog">Start Programming</string>
20 <string name="cancel">Cancel</string>
21 <!--
22 <string name="prog_ongoing">NB! Not permitted to close this view during OAD transfer</string>
23-->
24 <string name="label_progress">Progress:</string>
25 <string name="label_log">Log</string>
26
27 <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>
28
29
30 <string-array name="image_arrays">
31 <item>&lt;no image selected&gt; </item>
32 <item>Example Image</item>
33 <item>Browse...</item>
34 </string-array>
35
36 <!-- File activity -->
37 <string name="title_activity_file">FileActivity</string>
38 <string name="label_directory">Select a file from the external storage: </string>
39 <string name="btn_txt_confirm">Confirm</string>
40
41
42
43</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..18e58ec
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,40 @@
1<resources>
2
3 <!-- Base application theme. -->
4 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
5 <!-- Customize your theme here. -->
6 <item name="colorPrimary">@color/colorPrimary</item>
7 <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
8 <item name="colorAccent">@color/colorAccent</item>
9</style>
10
11 <style name="labelStyle">
12 <item name="android:textColor">@color/colorPrimaryDark</item>
13 <item name="android:textSize">18dp</item>
14 </style>
15
16 <style name="dataStyle">
17 <item name="android:textColor">#000</item>
18 <item name="android:textSize">16dp</item>
19 </style>
20
21 <style name="nameStyle">
22 <item name="android:textColor">#000</item>
23 <item name="android:textSize">14dp</item>
24 </style>
25
26 <style name="nameStyleSelected">
27 <item name="android:textColor">#3F51B5</item>
28 <item name="android:textSize">14dp</item>
29 </style>
30
31 <style name="AppTheme.NoActionBar">
32 <item name="windowActionBar">false</item>
33 <item name="windowNoTitle">true</item>
34 </style>
35
36 <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
37
38 <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
39
40</resources>
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..77ce66e
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,23 @@
1// Top-level build file where you can add configuration options common to all sub-projects/modules.
2
3buildscript {
4 repositories {
5 jcenter()
6 }
7 dependencies {
8 classpath 'com.android.tools.build:gradle:2.1.3'
9
10 // NOTE: Do not place your application dependencies here; they belong
11 // in the individual module build.gradle files
12 }
13}
14
15allprojects {
16 repositories {
17 jcenter()
18 }
19}
20
21task clean(type: Delete) {
22 delete rootProject.buildDir
23}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..3c8634d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
1#Wed Aug 17 10:55:21 CEST 2016
2distributionBase=GRADLE_USER_HOME
3distributionPath=wrapper/dists
4zipStoreBase=GRADLE_USER_HOME
5zipStorePath=wrapper/dists
6distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
1#!/usr/bin/env bash
2
3##############################################################################
4##
5## Gradle start up script for UN*X
6##
7##############################################################################
8
9# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10DEFAULT_JVM_OPTS=""
11
12APP_NAME="Gradle"
13APP_BASE_NAME=`basename "$0"`
14
15# Use the maximum available, or set MAX_FD != -1 to use that value.
16MAX_FD="maximum"
17
18warn ( ) {
19 echo "$*"
20}
21
22die ( ) {
23 echo
24 echo "$*"
25 echo
26 exit 1
27}
28
29# OS specific support (must be 'true' or 'false').
30cygwin=false
31msys=false
32darwin=false
33case "`uname`" in
34 CYGWIN* )
35 cygwin=true
36 ;;
37 Darwin* )
38 darwin=true
39 ;;
40 MINGW* )
41 msys=true
42 ;;
43esac
44
45# Attempt to set APP_HOME
46# Resolve links: $0 may be a link
47PRG="$0"
48# Need this for relative symlinks.
49while [ -h "$PRG" ] ; do
50 ls=`ls -ld "$PRG"`
51 link=`expr "$ls" : '.*-> \(.*\)$'`
52 if expr "$link" : '/.*' > /dev/null; then
53 PRG="$link"
54 else
55 PRG=`dirname "$PRG"`"/$link"
56 fi
57done
58SAVED="`pwd`"
59cd "`dirname \"$PRG\"`/" >/dev/null
60APP_HOME="`pwd -P`"
61cd "$SAVED" >/dev/null
62
63CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64
65# Determine the Java command to use to start the JVM.
66if [ -n "$JAVA_HOME" ] ; then
67 if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 # IBM's JDK on AIX uses strange locations for the executables
69 JAVACMD="$JAVA_HOME/jre/sh/java"
70 else
71 JAVACMD="$JAVA_HOME/bin/java"
72 fi
73 if [ ! -x "$JAVACMD" ] ; then
74 die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75
76Please set the JAVA_HOME variable in your environment to match the
77location of your Java installation."
78 fi
79else
80 JAVACMD="java"
81 which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82
83Please set the JAVA_HOME variable in your environment to match the
84location of your Java installation."
85fi
86
87# Increase the maximum file descriptors if we can.
88if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 MAX_FD_LIMIT=`ulimit -H -n`
90 if [ $? -eq 0 ] ; then
91 if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 MAX_FD="$MAX_FD_LIMIT"
93 fi
94 ulimit -n $MAX_FD
95 if [ $? -ne 0 ] ; then
96 warn "Could not set maximum file descriptor limit: $MAX_FD"
97 fi
98 else
99 warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 fi
101fi
102
103# For Darwin, add options to specify how the application appears in the dock
104if $darwin; then
105 GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106fi
107
108# For Cygwin, switch paths to Windows format before running java
109if $cygwin ; then
110 APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 JAVACMD=`cygpath --unix "$JAVACMD"`
113
114 # We build the pattern for arguments to be converted via cygpath
115 ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 SEP=""
117 for dir in $ROOTDIRSRAW ; do
118 ROOTDIRS="$ROOTDIRS$SEP$dir"
119 SEP="|"
120 done
121 OURCYGPATTERN="(^($ROOTDIRS))"
122 # Add a user-defined pattern to the cygpath arguments
123 if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 fi
126 # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 i=0
128 for arg in "$@" ; do
129 CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131
132 if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 else
135 eval `echo args$i`="\"$arg\""
136 fi
137 i=$((i+1))
138 done
139 case $i in
140 (0) set -- ;;
141 (1) set -- "$args0" ;;
142 (2) set -- "$args0" "$args1" ;;
143 (3) set -- "$args0" "$args1" "$args2" ;;
144 (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 esac
151fi
152
153# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154function splitJvmOpts() {
155 JVM_OPTS=("$@")
156}
157eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159
160exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
1@if "%DEBUG%" == "" @echo off
2@rem ##########################################################################
3@rem
4@rem Gradle startup script for Windows
5@rem
6@rem ##########################################################################
7
8@rem Set local scope for the variables with windows NT shell
9if "%OS%"=="Windows_NT" setlocal
10
11@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12set DEFAULT_JVM_OPTS=
13
14set DIRNAME=%~dp0
15if "%DIRNAME%" == "" set DIRNAME=.
16set APP_BASE_NAME=%~n0
17set APP_HOME=%DIRNAME%
18
19@rem Find java.exe
20if defined JAVA_HOME goto findJavaFromJavaHome
21
22set JAVA_EXE=java.exe
23%JAVA_EXE% -version >NUL 2>&1
24if "%ERRORLEVEL%" == "0" goto init
25
26echo.
27echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28echo.
29echo Please set the JAVA_HOME variable in your environment to match the
30echo location of your Java installation.
31
32goto fail
33
34:findJavaFromJavaHome
35set JAVA_HOME=%JAVA_HOME:"=%
36set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37
38if exist "%JAVA_EXE%" goto init
39
40echo.
41echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42echo.
43echo Please set the JAVA_HOME variable in your environment to match the
44echo location of your Java installation.
45
46goto fail
47
48:init
49@rem Get command-line arguments, handling Windowz variants
50
51if not "%OS%" == "Windows_NT" goto win9xME_args
52if "%@eval[2+2]" == "4" goto 4NT_args
53
54:win9xME_args
55@rem Slurp the command line arguments.
56set CMD_LINE_ARGS=
57set _SKIP=2
58
59:win9xME_args_slurp
60if "x%~1" == "x" goto execute
61
62set CMD_LINE_ARGS=%*
63goto execute
64
65:4NT_args
66@rem Get arguments from the 4NT Shell from JP Software
67set CMD_LINE_ARGS=%$
68
69:execute
70@rem Setup the command line
71
72set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73
74@rem Execute Gradle
75"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76
77:end
78@rem End local scope for the variables with windows NT shell
79if "%ERRORLEVEL%"=="0" goto mainEnd
80
81:fail
82rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83rem the _cmd.exe /c_ return code!
84if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85exit /b 1
86
87:mainEnd
88if "%OS%"=="Windows_NT" endlocal
89
90:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
include ':app'