diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 795ca5c..8ea4610 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,8 @@ android:versionCode="72" android:versionName="3.4.0"> + + @@ -27,8 +29,9 @@ android:icon="@drawable/star_of_life_48x48" android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" - android:theme="@style/Theme.AppCompat" - > + android:theme="@style/AppTheme" + > + diff --git a/app/src/main/java/uk/org/openseizuredetector/BLEScanActivity.java b/app/src/main/java/uk/org/openseizuredetector/BLEScanActivity.java new file mode 100644 index 0000000..7d2daf1 --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/BLEScanActivity.java @@ -0,0 +1,323 @@ +package uk.org.openseizuredetector; + +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import android.Manifest; +import android.app.Activity; +import android.app.ListActivity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.support.v4.app.ActivityCompat; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.ArrayList; + +/** + * Activity for scanning and displaying available Bluetooth LE devices. + */ +public class BLEScanActivity extends ListActivity { + private LeDeviceListAdapter mLeDeviceListAdapter; + private BluetoothAdapter mBluetoothAdapter; + private boolean mScanning; + private Handler mHandler; + + private boolean mPermissionsRequested = false; + private final String TAG = "BLEScanActivity"; + + private final String[] REQUIRED_PERMISSIONS = { + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.BLUETOOTH, + Manifest.permission.BLUETOOTH_ADMIN, + }; + + private static final int REQUEST_ENABLE_BT = 1; + // Stops scanning after 10 seconds. + private static final long SCAN_PERIOD = 10000; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //this.getActionBar().setTitle(R.string.title_devices); + this.setTitle(R.string.title_devices); + mHandler = new Handler(); + + // Use this check to determine whether BLE is supported on the device. Then you can + // selectively disable BLE-related features. + if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { + Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); + finish(); + } + + // Initializes a Bluetooth adapter. For API level 18 and above, get a reference to + // BluetoothAdapter through BluetoothManager. + final BluetoothManager bluetoothManager = + (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + mBluetoothAdapter = bluetoothManager.getAdapter(); + + // Checks if Bluetooth is supported on the device. + if (mBluetoothAdapter == null) { + Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show(); + finish(); + return; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.ble_scan_menu, menu); + if (!mScanning) { + menu.findItem(R.id.menu_stop).setVisible(false); + menu.findItem(R.id.menu_scan).setVisible(true); + menu.findItem(R.id.menu_refresh).setActionView(null); + } else { + menu.findItem(R.id.menu_stop).setVisible(true); + menu.findItem(R.id.menu_scan).setVisible(false); + menu.findItem(R.id.menu_refresh).setActionView( + R.layout.actionbar_indeterminate_progress); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_scan: + mLeDeviceListAdapter.clear(); + scanLeDevice(true); + break; + case R.id.menu_stop: + scanLeDevice(false); + break; + } + return true; + } + + @Override + protected void onResume() { + super.onResume(); + + // Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled, + // fire an intent to display a dialog asking the user to grant permission to enable it. + if (!mBluetoothAdapter.isEnabled()) { + Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); + } + + // Initializes list view adapter. + mLeDeviceListAdapter = new LeDeviceListAdapter(); + setListAdapter(mLeDeviceListAdapter); + + scanLeDevice(true); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // User chose not to enable Bluetooth. + if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) { + finish(); + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onPause() { + super.onPause(); + scanLeDevice(false); + mLeDeviceListAdapter.clear(); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position); + if (device == null) return; + Log.v(TAG, "onListItemClick: Device=" + device.getName() + ", Addr=" + device.getAddress()); + if (mScanning) { + mBluetoothAdapter.stopLeScan(mLeScanCallback); + mScanning = false; + } + Log.v(TAG,"Saving Device Details"); + SharedPreferences.Editor SPE = PreferenceManager + .getDefaultSharedPreferences(this).edit(); + try { + SPE.putString("BLE_Device_Addr", device.getAddress()); + SPE.putString("BLE_Device_Name", device.getName()); + SPE.apply(); + SPE.commit(); + + Log.v(TAG, "Saved Device Name="+device.getName()+" and Address="+device.getAddress()); + } catch (Exception ex) { + Log.e(TAG, "Error Saving Devie Name and Address!"); + Toast toast = Toast.makeText(this, "Problem Saving Device Name and Address", Toast.LENGTH_SHORT); + toast.show(); + } + SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences((this)); + Log.v(TAG,"Check of saved values - Name="+SP.getString("BLE_Device_Name","NOT SET")+", Addr="+SP.getString("BLE_Device_Addr","NOT SET")); + + finish(); + } + + private void scanLeDevice(final boolean enable) { + requestPermissions(this); + if (enable) { + // Stops scanning after a pre-defined scan period. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mScanning = false; + mBluetoothAdapter.stopLeScan(mLeScanCallback); + invalidateOptionsMenu(); + } + }, SCAN_PERIOD); + + mScanning = true; + mBluetoothAdapter.startLeScan(mLeScanCallback); + } else { + mScanning = false; + mBluetoothAdapter.stopLeScan(mLeScanCallback); + } + invalidateOptionsMenu(); + } + + // Adapter for holding devices found through scanning. + private class LeDeviceListAdapter extends BaseAdapter { + private ArrayList mLeDevices; + private LayoutInflater mInflator; + + public LeDeviceListAdapter() { + super(); + mLeDevices = new ArrayList(); + mInflator = BLEScanActivity.this.getLayoutInflater(); + } + + public void addDevice(BluetoothDevice device) { + if (!mLeDevices.contains(device)) { + Log.v(TAG,"addDevice - "+device.getName()); + mLeDevices.add(device); + } + } + + public BluetoothDevice getDevice(int position) { + return mLeDevices.get(position); + } + + public void clear() { + mLeDevices.clear(); + } + + @Override + public int getCount() { + return mLeDevices.size(); + } + + @Override + public Object getItem(int i) { + return mLeDevices.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + ViewHolder viewHolder; + Log.v(TAG,"scanner getView i="+i); + // General ListView optimization code. + if (view == null) { + view = mInflator.inflate(R.layout.ble_list_item_device, null); + viewHolder = new ViewHolder(); + viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address); + viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name); + view.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) view.getTag(); + } + + BluetoothDevice device = mLeDevices.get(i); + final String deviceName = device.getName(); + if (deviceName != null && deviceName.length() > 0) + viewHolder.deviceName.setText(deviceName); + else + viewHolder.deviceName.setText(R.string.unknown_device); + viewHolder.deviceAddress.setText(device.getAddress()); + + return view; + } + } + + // Device scan callback. + private BluetoothAdapter.LeScanCallback mLeScanCallback = + new BluetoothAdapter.LeScanCallback() { + + @Override + public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { + Log.v(TAG,"LEScanCallback - device="+device.getName()); + runOnUiThread(new Runnable() { + @Override + public void run() { + mLeDeviceListAdapter.addDevice(device); + mLeDeviceListAdapter.notifyDataSetChanged(); + } + }); + } + }; + + static class ViewHolder { + TextView deviceName; + TextView deviceAddress; + } + + + public void requestPermissions(Activity activity) { + if (mPermissionsRequested) { + Log.i(TAG, "requestPermissions() - request already sent - not doing anything"); + } else { + Log.i(TAG, "requestPermissions() - requesting permissions"); + for (int i = 0; i < REQUIRED_PERMISSIONS.length; i++) { + if (ActivityCompat.shouldShowRequestPermissionRationale(activity, + REQUIRED_PERMISSIONS[i])) { + Log.i(TAG, "shouldShowRationale for permission" + REQUIRED_PERMISSIONS[i]); + } + } + ActivityCompat.requestPermissions(activity, + REQUIRED_PERMISSIONS, + 42); + mPermissionsRequested = true; + } + } +} diff --git a/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java b/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java index 6f402bb..62f31b2 100644 --- a/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java @@ -34,16 +34,19 @@ import android.os.Bundle; import android.preference.PreferenceFragment; import android.preference.PreferenceManager; import android.util.Log; +import android.view.View; +import android.widget.Button; import android.widget.Toast; import java.util.List; -public class PrefActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { +public class PrefActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener { private String TAG = "PreferenceActivity"; private OsdUtil mUtil; private boolean mPrefChanged = false; private Context mContext; private Handler mHandler; + private Button mSelectBLEButton; @Override protected void onCreate(Bundle savedInstanceState) { @@ -118,6 +121,8 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference } } + //mSelectBLEButton = findViewById(R.id.selectBLEDeviceButton); + //mSelectBLEButton.setOnClickListener(this); } @@ -217,6 +222,20 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference return true; } + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.selectBLEDeviceButton: + Log.v(TAG,"onClick - SelectBLEDeviceButton"); + final Intent intent = new Intent(this.mContext, BLEScanActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + break; + default: + Log.e(TAG,"onClick - unrecognised button"); + } + } + /** * This fragment shows the preferences for the first header. */ diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java index 1b4b6e0..607b947 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java @@ -99,8 +99,8 @@ public abstract class SdDataSource { private int mAlarmCount; - - + protected String mBleDeviceAddr; + protected String mBleDeviceName; public SdDataSource(Context context, Handler handler, SdDataReceiver sdDataReceiver) { @@ -660,6 +660,12 @@ public abstract class SdDataSource { // Watch Settings String prefStr; + prefStr = SP.getString("BLE_Device_Addr", "SET_FROM_XML"); + mBleDeviceAddr = prefStr; + Log.v(TAG,"mBLEDeviceAddr="+mBleDeviceAddr); + prefStr = SP.getString("BLE_Device_Name", "SET_FROM_XML"); + mBleDeviceName = prefStr; + Log.v(TAG,"mBLEDeviceName="+mBleDeviceName); prefStr = SP.getString("PebbleDebug", "SET_FROM_XML"); if (prefStr != null) { diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java index 7a07a13..9b0e68c 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java @@ -70,8 +70,15 @@ public class SdDataSourceBLE extends SdDataSource { */ public void start() { Log.i(TAG, "start()"); - mUtil.writeToSysLogFile("SdDataSourceBLE.start()"); super.start(); + mUtil.writeToSysLogFile("SdDataSourceBLE.start() - mBleDeviceAddr="+mBleDeviceAddr); + + if (mBleDeviceAddr == "" || mBleDeviceAddr == null) { + final Intent intent = new Intent(this.mContext, BLEScanActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + Log.i(TAG,"mBLEDevice is "+mBleDeviceName+", Addr="+mBleDeviceAddr); } /** diff --git a/app/src/main/res/layout/actionbar_indeterminate_progress.xml b/app/src/main/res/layout/actionbar_indeterminate_progress.xml new file mode 100644 index 0000000..edaac11 --- /dev/null +++ b/app/src/main/res/layout/actionbar_indeterminate_progress.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/ble_list_item_device.xml b/app/src/main/res/layout/ble_list_item_device.xml new file mode 100644 index 0000000..255e412 --- /dev/null +++ b/app/src/main/res/layout/ble_list_item_device.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pref_select_ble_device_button.xml b/app/src/main/res/layout/pref_select_ble_device_button.xml new file mode 100644 index 0000000..d6718b1 --- /dev/null +++ b/app/src/main/res/layout/pref_select_ble_device_button.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/ble_scan_menu.xml b/app/src/main/res/menu/ble_scan_menu.xml new file mode 100644 index 0000000..8b934f1 --- /dev/null +++ b/app/src/main/res/menu/ble_scan_menu.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 06668b7..4825463 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -141,5 +141,13 @@ Send SMS - last location is ERROR: FAILED TO SEND SMS MESSAGE SMS Alarms Disabled - not doing anything! + BLE Devices + BLE Not Supported + Bluetooth Not Supported + STOP + SCAN + Unknown Device + Select BLE Device + Select Bluetooth Low Energy (BLE) Device to provide seizure (acceleration and heart rate) data). diff --git a/app/src/main/res/xml/general_prefs.xml b/app/src/main/res/xml/general_prefs.xml index 7965b12..f0bb819 100644 --- a/app/src/main/res/xml/general_prefs.xml +++ b/app/src/main/res/xml/general_prefs.xml @@ -8,6 +8,12 @@ android:entryValues="@array/datasource_list_values" android:defaultValue="Pebble" android:dialogTitle="Select Data Source" /> + - - + + + + + + + android:key="PebbleSdMode" + android:summary="Select one of the three available modes of operation." + android:title="Seizure Detector Mode" /> + android:entries="@array/pebble_sample_freq_list" + android:entryValues="@array/pebble_sample_freq_list_values" + android:key="SampleFreq" + android:summary="Higher Frequency is more Accurate, but uses more battery power." + android:title="Select Sample Frequency" /> @@ -102,16 +111,15 @@ android:title="Fall Detection Window (milli-seconds)" /> - + + android:key="PebbleDebug" + android:summary="Set Debug mode on or off." + android:title="Seizure Detector Debug Mode" />