From acaebd4288dbcefcbd4497f0f15df7d2fa144c11 Mon Sep 17 00:00:00 2001 From: Constantine Mars Date: Thu, 7 Jan 2016 12:33:02 +0200 Subject: [PATCH 01/21] Hotfix for Android M BLE scanning malfunction. Incomplete --- app/build.gradle | 14 +- app/src/main/AndroidManifest.xml | 3 + .../dataart/btle_android/MainActivity.java | 168 +++++++----- .../btle_android/PermissionsHelper.java | 242 ++++++++++++++++++ 4 files changed, 356 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/com/dataart/btle_android/PermissionsHelper.java diff --git a/app/build.gradle b/app/build.gradle index 8ca843c..7908a6c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,15 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 18 - buildToolsVersion "21.1.2" + compileSdkVersion 23 + buildToolsVersion "23.0.1" defaultConfig { applicationId "com.dataart.btle_android" minSdkVersion 18 - targetSdkVersion 21 + targetSdkVersion 23 versionCode 2 - versionName "2.0" + versionName "2.1" } buildTypes { release { @@ -21,12 +21,14 @@ android { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } + useLibrary 'org.apache.http.legacy' } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:support-v4:21.0.3' - compile 'com.google.code.gson:gson:2.3' + compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.google.code.gson:gson:2.4' compile 'com.jakewharton.timber:timber:2.7.1' compile 'commons-codec:commons-codec:1.5' + compile 'com.google.android.gms:play-services:8.4.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9eab165..8263346 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,6 +12,9 @@ + + + , GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { + public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000; + public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = + UPDATE_INTERVAL_IN_MILLISECONDS / 2; + private static final int REQUEST_CHECK_SETTINGS = 100; + protected LocationSettingsRequest mLocationSettingsRequest; + protected GoogleApiClient mGoogleApiClient; + protected LocationRequest mLocationRequest; + private LocationEnabledListener listener; + private Activity activity; + private boolean waitForResume = false; + + public PermissionsHelper(LocationEnabledListener listener, Activity activity) { + this.listener = listener; + this.activity = activity; + buildGoogleApiClient(); + createLocationRequest(); + buildLocationSettingsRequest(); + } + + public void start() { + mGoogleApiClient.connect(); + } + + protected void stop() { + mGoogleApiClient.disconnect(); + } + + /** + * Build request to enable Location + */ + private void buildLocationSettingsRequest() { + LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); + builder.addLocationRequest(mLocationRequest); + mLocationSettingsRequest = builder.build(); + } + + private void buildGoogleApiClient() { + mGoogleApiClient = new GoogleApiClient.Builder(activity) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .addApi(LocationServices.API) + .build(); + } + + /** + * Location request is necessary for defining which exactly permissions must be enabled + */ + protected void createLocationRequest() { + mLocationRequest = new LocationRequest(); + mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS); + mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS); + mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + } + + @Override + public void onResult(@NonNull LocationSettingsResult locationSettingsResult) { + final Status status = locationSettingsResult.getStatus(); + switch (status.getStatusCode()) { + case LocationSettingsStatusCodes.SUCCESS: + Timber.i("All location settings are satisfied."); + listener.onLocationEnabled(); + break; + case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: + Timber.i("Location settings are not satisfied. Show the user a dialog to" + + "upgrade location settings "); + + try { + // Show the dialog by calling startResolutionForResult(), and check the result + // in onActivityResult(). + status.startResolutionForResult(activity, REQUEST_CHECK_SETTINGS); + } catch (IntentSender.SendIntentException e) { + Timber.i("PendingIntent unable to execute request."); + } + break; + case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: + Timber.i("Location settings are inadequate, and cannot be fixed here. Dialog " + + "not created."); + break; + } + } + + @Override + public void onConnected(@Nullable Bundle bundle) { + + } + + @Override + public void onConnectionSuspended(int i) { + + } + + @Override + public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { + + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + // Check for the integer request code originally supplied to startResolutionForResult(). + case REQUEST_CHECK_SETTINGS: + switch (resultCode) { + case Activity.RESULT_OK: + Timber.i("User agreed to make required location settings changes."); + listener.onLocationEnabled(); + break; + case Activity.RESULT_CANCELED: + Timber.i("User chose not to make required location settings changes."); + break; + } + break; + } + } + + private boolean isLocationEnabled() { + LocationManager lm = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE); + boolean gps_enabled = false; + boolean network_enabled = false; + + try { + gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER); + } catch (Exception ignored) { + } + + try { + network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + } catch (Exception ignored) { + } + + return gps_enabled || network_enabled; + } + + protected void checkLocationEnabled() { + if (isLocationEnabled()) { + listener.onLocationEnabled(); + return; + } + +// If location isn't enabled - check whether we can call Google Play Services or user to manually +// switch Location + final int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity.getApplicationContext()); + if (status == ConnectionResult.SUCCESS) { + PendingResult result = + LocationServices.SettingsApi.checkLocationSettings( + mGoogleApiClient, + mLocationSettingsRequest + ); + result.setResultCallback(this); + } else { +// If no services available - the only thing we can do is to +// ask user to switch Location manually + activity.startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + waitForResume = true; + } + } + + public void resume() { + if (waitForResume) { + checkLocationEnabled(); + } + } + + public interface LocationEnabledListener { + void onLocationEnabled(); + } + +} +// private AppCompatActivity activity; +// private static Integer requestCodeCounter = 0; +// +// public static Integer requestPermission(Activity activity, String permission) { +// +// // Here, thisActivity is the current activity +// if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { +// return null; +// } +// +// Integer requestCode = requestCodeCounter++; +// // Should we show an explanation? +// if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { +// +// // Show an expanation to the user *asynchronously* -- don't block +// // this thread waiting for the user's response! After the user +// // sees the explanation, try again to request the permission. +// +// } else { +// +// // No explanation needed, we can request the permission. +// +// ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode); +// +// // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an +// // app-defined int constant. The callback method gets the +// // result of the request. +// } +// +// return requestCode; +// } +// +// public static void onRequestPermissionsResult(OnPermissionGranted onPermissionGranted, final Integer expectedCode, int requestCode, String permissions[], int[] grantResults) { +// +// if(expectedCode != null && expectedCode == requestCode) { +// boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; +// onPermissionGranted.call(granted); +// } +// } +// +// public interface OnPermissionGranted{ +// void call(boolean granted); +// } + From 7177351044a70d979b8e0b0a5b5e99b539c16f22 Mon Sep 17 00:00:00 2001 From: Constantine Mars Date: Wed, 1 Jun 2016 18:40:19 +0300 Subject: [PATCH 02/21] Code cleanup --- .../dataart/btle_android/ApplicationTest.java | 13 --- .../dataart/btle_android/MainActivity.java | 40 ++------ .../btle_android/PermissionsHelper.java | 98 ++++++------------- .../gatt/callbacks/StatusJson.java | 70 ++++++------- app/src/main/res/values/strings.xml | 12 ++- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 7 files changed, 83 insertions(+), 156 deletions(-) delete mode 100644 app/src/androidTest/java/com/dataart/btle_android/ApplicationTest.java diff --git a/app/src/androidTest/java/com/dataart/btle_android/ApplicationTest.java b/app/src/androidTest/java/com/dataart/btle_android/ApplicationTest.java deleted file mode 100644 index e4aae39..0000000 --- a/app/src/androidTest/java/com/dataart/btle_android/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.dataart.btle_android; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/dataart/btle_android/MainActivity.java b/app/src/main/java/com/dataart/btle_android/MainActivity.java index 0735f95..e2f3ba3 100644 --- a/app/src/main/java/com/dataart/btle_android/MainActivity.java +++ b/app/src/main/java/com/dataart/btle_android/MainActivity.java @@ -13,7 +13,6 @@ import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; -import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; @@ -56,12 +55,6 @@ public void onReceive(Context context, Intent intent) { private Button restartServiceButton; private BTLEDevicePreferences prefs; private boolean isServiceStarted; - LocationEnabledListener locationEnabledListener = new LocationEnabledListener() { - @Override - public void onLocationEnabled() { - startService(); - } - }; private final View.OnClickListener restartClickListener = new View.OnClickListener() { @Override public void onClick(View view) { @@ -97,19 +90,20 @@ public void afterTextChanged(Editable editable) { onDataChanged(); } }; + private LocationEnabledListener locationEnabledListener = new LocationEnabledListener() { + @Override + public void onLocationEnabled() { + startService(); + } + }; private PermissionsHelper permissionsHelper; private final View.OnClickListener serviceClickListener = new View.OnClickListener() { @Override public void onClick(View v) { permissionsHelper.checkLocationEnabled(); -// expectedCode = PermissionsHelper.requestPermission(MainActivity.this, Manifest.permission.BLUETOOTH_PRIVILEGED); -// if (expectedCode == null) { -// startService(); -// } } }; - private Integer expectedCode; @Override protected void onCreate(Bundle savedInstanceState) { @@ -167,17 +161,17 @@ private boolean isBluetoothLeSupported() { return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); } - public boolean isBluetoothSupported() { + private boolean isBluetoothSupported() { if (mBluetoothManager == null) { mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { - Log.e(TAG, "Unable to initialize BluetoothManager"); + Timber.e(getString(R.string.bt_unable_init)); return false; } } mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { - Log.e(TAG, "Unable to obtain a BluetoothAdapter"); + Timber.e(getString(R.string.bt_unable_get_btm)); return false; } return true; @@ -216,24 +210,10 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); } -// -// @Override -// public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { -// PermissionsHelper.onRequestPermissionsResult(new PermissionsHelper.OnPermissionGranted() { -// @Override -// public void call(boolean granted) { -// if(granted) { -// startService(); -// } -// } -// }, expectedCode, requestCode, permissions, grantResults); -// } @Override protected void onDestroy() { - if (mReceiver != null) { - unregisterReceiver(mReceiver); - } + unregisterReceiver(mReceiver); super.onDestroy(); } diff --git a/app/src/main/java/com/dataart/btle_android/PermissionsHelper.java b/app/src/main/java/com/dataart/btle_android/PermissionsHelper.java index a0d5014..e615469 100644 --- a/app/src/main/java/com/dataart/btle_android/PermissionsHelper.java +++ b/app/src/main/java/com/dataart/btle_android/PermissionsHelper.java @@ -29,19 +29,18 @@ * Android M with Google Play Services requires Location enabled before starting BLE devices discovery * This helper implements turning on Location services programmatically */ -public class PermissionsHelper implements ResultCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { - public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000; - public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = - UPDATE_INTERVAL_IN_MILLISECONDS / 2; +class PermissionsHelper implements ResultCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { + private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000; + private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2; private static final int REQUEST_CHECK_SETTINGS = 100; - protected LocationSettingsRequest mLocationSettingsRequest; - protected GoogleApiClient mGoogleApiClient; - protected LocationRequest mLocationRequest; + private LocationSettingsRequest mLocationSettingsRequest; + private GoogleApiClient mGoogleApiClient; + private LocationRequest mLocationRequest; private LocationEnabledListener listener; private Activity activity; private boolean waitForResume = false; - public PermissionsHelper(LocationEnabledListener listener, Activity activity) { + PermissionsHelper(LocationEnabledListener listener, Activity activity) { this.listener = listener; this.activity = activity; buildGoogleApiClient(); @@ -77,7 +76,7 @@ private void buildGoogleApiClient() { /** * Location request is necessary for defining which exactly permissions must be enabled */ - protected void createLocationRequest() { + private void createLocationRequest() { mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS); mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS); @@ -89,54 +88,48 @@ public void onResult(@NonNull LocationSettingsResult locationSettingsResult) { final Status status = locationSettingsResult.getStatus(); switch (status.getStatusCode()) { case LocationSettingsStatusCodes.SUCCESS: - Timber.i("All location settings are satisfied."); + log(R.string.location_satisfied); listener.onLocationEnabled(); break; case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: - Timber.i("Location settings are not satisfied. Show the user a dialog to" + - "upgrade location settings "); + log(R.string.location_unsatisfied); try { - // Show the dialog by calling startResolutionForResult(), and check the result - // in onActivityResult(). + // Show the dialog by calling startResolutionForResult(), and check the result in onActivityResult(). status.startResolutionForResult(activity, REQUEST_CHECK_SETTINGS); } catch (IntentSender.SendIntentException e) { - Timber.i("PendingIntent unable to execute request."); + log(R.string.intent_not_started); } break; case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: - Timber.i("Location settings are inadequate, and cannot be fixed here. Dialog " + - "not created."); + log(R.string.location_cant_be_fixed); break; } } @Override public void onConnected(@Nullable Bundle bundle) { - } @Override public void onConnectionSuspended(int i) { - } @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { - } - public void onActivityResult(int requestCode, int resultCode, Intent data) { + void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { // Check for the integer request code originally supplied to startResolutionForResult(). case REQUEST_CHECK_SETTINGS: switch (resultCode) { case Activity.RESULT_OK: - Timber.i("User agreed to make required location settings changes."); + log(R.string.location_changes_applied); listener.onLocationEnabled(); break; case Activity.RESULT_CANCELED: - Timber.i("User chose not to make required location settings changes."); + log(R.string.loccation_changes_not_applied); break; } break; @@ -161,7 +154,7 @@ private boolean isLocationEnabled() { return gps_enabled || network_enabled; } - protected void checkLocationEnabled() { + void checkLocationEnabled() { if (isLocationEnabled()) { listener.onLocationEnabled(); return; @@ -169,6 +162,7 @@ protected void checkLocationEnabled() { // If location isn't enabled - check whether we can call Google Play Services or user to manually // switch Location + final int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity.getApplicationContext()); if (status == ConnectionResult.SUCCESS) { PendingResult result = @@ -185,58 +179,22 @@ protected void checkLocationEnabled() { } } - public void resume() { + void resume() { if (waitForResume) { checkLocationEnabled(); } } - public interface LocationEnabledListener { - void onLocationEnabled(); + private String getString(int id) { + return activity.getString(id); } + private void log(int id) { + Timber.i(getString(id)); + } + + interface LocationEnabledListener { + void onLocationEnabled(); + } } -// private AppCompatActivity activity; -// private static Integer requestCodeCounter = 0; -// -// public static Integer requestPermission(Activity activity, String permission) { -// -// // Here, thisActivity is the current activity -// if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { -// return null; -// } -// -// Integer requestCode = requestCodeCounter++; -// // Should we show an explanation? -// if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { -// -// // Show an expanation to the user *asynchronously* -- don't block -// // this thread waiting for the user's response! After the user -// // sees the explanation, try again to request the permission. -// -// } else { -// -// // No explanation needed, we can request the permission. -// -// ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode); -// -// // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an -// // app-defined int constant. The callback method gets the -// // result of the request. -// } -// -// return requestCode; -// } -// -// public static void onRequestPermissionsResult(OnPermissionGranted onPermissionGranted, final Integer expectedCode, int requestCode, String permissions[], int[] grantResults) { -// -// if(expectedCode != null && expectedCode == requestCode) { -// boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; -// onPermissionGranted.call(granted); -// } -// } -// -// public interface OnPermissionGranted{ -// void call(boolean granted); -// } diff --git a/app/src/main/java/com/dataart/btle_android/btle_gateway/gatt/callbacks/StatusJson.java b/app/src/main/java/com/dataart/btle_android/btle_gateway/gatt/callbacks/StatusJson.java index b5e2843..2b1695e 100644 --- a/app/src/main/java/com/dataart/btle_android/btle_gateway/gatt/callbacks/StatusJson.java +++ b/app/src/main/java/com/dataart/btle_android/btle_gateway/gatt/callbacks/StatusJson.java @@ -1,28 +1,34 @@ package com.dataart.btle_android.btle_gateway.gatt.callbacks; -import android.os.Parcel; -import android.os.Parcelable; - import com.dataart.btle_android.BTLEApplication; import com.dataart.btle_android.R; -import org.apache.commons.codec.binary.Hex; - /** * Created by Constantine Mars on 4/3/15. * * Wraps data for json conversion */ -public abstract class StatusJson { - public static class Status { +abstract class StatusJson { + static String bytes2String(byte[] value) { + String s = ""; + for (byte b : value) { + if (!s.isEmpty()) { + s += ", "; + } + s += String.format("0x%02X", b); + } + return s; + } + + static class Status { private String status; - public Status(String status) { + Status(String status) { this.status = status; } - public static Status statusOk() { - return new Status(BTLEApplication.getApplication().getString(R.string.status_ok)); + static Status statusOk() { + return new Status(BTLEApplication.getApplication().getString(android.R.string.ok)); } public static Status statusOkWithVal(String val) { @@ -30,14 +36,14 @@ public static Status statusOkWithVal(String val) { } public static Status statusFail() { - return new Status(BTLEApplication.getApplication().getString(R.string.status_ok)); + return new Status(BTLEApplication.getApplication().getString(android.R.string.ok)); } public static Status statusFailWithVal(String val) { return new Status(val); } - public static Status statusTimeoutReached() { + static Status statusTimeoutReached() { return new Status(BTLEApplication.getApplication().getString(R.string.status_timeout)); } @@ -46,40 +52,31 @@ public String getStatus() { } } - public static class StatusWithObject { + static class StatusWithObject { private Object result; - public StatusWithObject(Object result) { + StatusWithObject(Object result) { this.result = result; } - public static StatusWithObject statusWithObject(Object object){ + static StatusWithObject statusWithObject(Object object) { return new StatusWithObject(object); } } - public static class FullStatusWithVal extends FullStatus { + static class FullStatusWithVal extends FullStatus { private String value; - public FullStatusWithVal(String status, String device, String serviceUUID, String characteristicUUID, String value) { + FullStatusWithVal(String status, String device, String serviceUUID, String characteristicUUID, String value) { super(status, device, serviceUUID, characteristicUUID); this.value = value; } } - public static class FullStatus { + static class FullStatus { private String status; private String device; private String serviceUUID; - - public String getDevice() { - return device; - } - - public String getStatus() { - return status; - } - private String characteristicUUID; public FullStatus(String status, String device, String serviceUUID, String characteristicUUID) { @@ -88,6 +85,14 @@ public FullStatus(String status, String device, String serviceUUID, String chara this.serviceUUID = serviceUUID; this.characteristicUUID = characteristicUUID; } + + public String getDevice() { + return device; + } + + public String getStatus() { + return status; + } } public static class FullStatusWithByteArray extends FullStatus { @@ -98,15 +103,4 @@ public FullStatusWithByteArray(String status, byte[] value, String device, Strin this.value = value; } } - - public static String bytes2String(byte[] value){ - String s = ""; - for(byte b:value){ - if(!s.isEmpty()){ - s+=", "; - } - s+=String.format("0x%02X",b); - } - return s; - } } \ 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 76b733c..d796123 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,13 +36,12 @@ Unknown device Bluetooth is off. Please turn on Bluetooth to continue working with DeviceHive BLE Gateway - BLE Service is working... + BLE Service is working… success fail not found - ok fail waiting for completion write not permitted @@ -71,4 +70,13 @@ invalid args. service UUID required invalid args. characteristic UUID required invalid args. write operation requires value + + All location settings are satisfied + Location settings are not satisfied. Show the user a dialog to upgrade location settings + PendingIntent unable to execute request + Location settings are inadequate, and cannot be fixed here. Dialog not created + User agreed to make required location settings changes + User chose not to make required location settings changes + Unable to initialize BluetoothManager + Unable to obtain a BluetoothAdapter diff --git a/build.gradle b/build.gradle index 1b7886d..b945a64 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.0' + classpath 'com.android.tools.build:gradle:2.2.0-alpha2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c71e76..cfa5739 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Wed Jun 01 11:00:23 EEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip From c9e263d6b94cc856dd1137ff2bc76a25d3f2b9bf Mon Sep 17 00:00:00 2001 From: Constantine Mars Date: Fri, 3 Jun 2016 01:54:27 +0300 Subject: [PATCH 03/21] Hotfixes for screen rotation, checks for SDK version < 18, App Theme --- app/build.gradle | 1 + .../dataart/btle_android/BTLEApplication.java | 8 +- .../dataart/btle_android/MainActivity.java | 62 +++++- .../btle_android/btle_gateway/build.gradle | 0 .../helpers/PermissionsHelper.java | 201 ++++++++++++++++++ app/src/main/res/layout/activity_settings.xml | 29 +-- app/src/main/res/values-v21/styles.xml | 8 +- app/src/main/res/values/colors.xml | 9 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 8 +- 10 files changed, 302 insertions(+), 27 deletions(-) delete mode 100644 app/src/main/java/com/dataart/btle_android/btle_gateway/build.gradle create mode 100644 app/src/main/java/com/dataart/btle_android/helpers/PermissionsHelper.java create mode 100644 app/src/main/res/values/colors.xml diff --git a/app/build.gradle b/app/build.gradle index 7908a6c..4a3621c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,6 +27,7 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:design:23.1.1' compile 'com.google.code.gson:gson:2.4' compile 'com.jakewharton.timber:timber:2.7.1' compile 'commons-codec:commons-codec:1.5' diff --git a/app/src/main/java/com/dataart/btle_android/BTLEApplication.java b/app/src/main/java/com/dataart/btle_android/BTLEApplication.java index 0de2d65..b21f031 100644 --- a/app/src/main/java/com/dataart/btle_android/BTLEApplication.java +++ b/app/src/main/java/com/dataart/btle_android/BTLEApplication.java @@ -14,6 +14,10 @@ public class BTLEApplication extends Application { private BTLEDeviceHive device; + public static BTLEApplication getApplication() { + return application; + } + @Override public void onCreate() { super.onCreate(); @@ -22,10 +26,6 @@ public void onCreate() { device = BTLEDeviceHive.newInstance(getApplicationContext()); } - public static BTLEApplication getApplication() { - return application; - } - public BTLEDeviceHive getDevice() { return device; } diff --git a/app/src/main/java/com/dataart/btle_android/MainActivity.java b/app/src/main/java/com/dataart/btle_android/MainActivity.java index e2f3ba3..8c274c5 100644 --- a/app/src/main/java/com/dataart/btle_android/MainActivity.java +++ b/app/src/main/java/com/dataart/btle_android/MainActivity.java @@ -2,13 +2,16 @@ import android.app.Activity; import android.app.ActivityManager; +import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; @@ -90,12 +93,14 @@ public void afterTextChanged(Editable editable) { onDataChanged(); } }; + private LocationEnabledListener locationEnabledListener = new LocationEnabledListener() { @Override public void onLocationEnabled() { startService(); } }; + private PermissionsHelper permissionsHelper; private final View.OnClickListener serviceClickListener = new View.OnClickListener() { @@ -111,8 +116,25 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_settings); Timber.plant(new Timber.DebugTree()); +// This extra check warns developers who try to lower SDK version for app + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + alertSdkVersionMismatch(new Runnable() { + @Override + public void run() { + finish(); + System.exit(0); + } + }); + + return; + } + permissionsHelper = new PermissionsHelper(locationEnabledListener, this); + init(); + } + + private void init() { if (getActionBar() != null) { getActionBar().setTitle(R.string.app_name); } @@ -186,6 +208,10 @@ private void requestEnableBluetooth() { protected void onResume() { super.onResume(); + if (permissionsHelper == null || mBluetoothAdapter == null) { + return; + } + permissionsHelper.resume(); // Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled, @@ -213,7 +239,12 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { @Override protected void onDestroy() { - unregisterReceiver(mReceiver); + + try { + unregisterReceiver(mReceiver); + } catch (IllegalArgumentException ex) { + Timber.e(ex.getMessage()); + } super.onDestroy(); } @@ -270,11 +301,13 @@ private void onDataChanged() { } private void resetValues() { - serverUrlEditText.setText(prefs.getServerUrl()); + serverUrlEditText.setText("http://playground.devicehive.com/api/rest"); +// prefs.getServerUrl()); gatewayIdEditText.setText(TextUtils.isEmpty(prefs.getGatewayId()) ? getString(R.string.default_gateway_id) : prefs.getGatewayId()); - accessKeyEditText.setText(TextUtils.isEmpty(prefs.getAccessKey()) ? - "" : prefs.getAccessKey()); + accessKeyEditText.setText("EpdvN2cWKWD5zn4M7Zv3B19zgzszbhCfeOb5OIr+XoE="); +// TextUtils.isEmpty(prefs.getAccessKey()) ? +// "" : prefs.getAccessKey()); } private void saveValues() { @@ -307,12 +340,29 @@ public void onDeviceFailedToSendNotification(Notification notification) { @Override protected void onStart() { super.onStart(); - permissionsHelper.start(); + if (permissionsHelper != null) { + permissionsHelper.start(); + } } @Override protected void onStop() { - permissionsHelper.stop(); + if (permissionsHelper != null) { + permissionsHelper.stop(); + } super.onStop(); } + + private void alertSdkVersionMismatch(final Runnable runnable) { + new AlertDialog.Builder(this) + .setTitle(R.string.sdk_version_warning_title) + .setMessage(R.string.sdk_version_warning) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + runnable.run(); + } + }) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + } } diff --git a/app/src/main/java/com/dataart/btle_android/btle_gateway/build.gradle b/app/src/main/java/com/dataart/btle_android/btle_gateway/build.gradle deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/dataart/btle_android/helpers/PermissionsHelper.java b/app/src/main/java/com/dataart/btle_android/helpers/PermissionsHelper.java new file mode 100644 index 0000000..9f54fa5 --- /dev/null +++ b/app/src/main/java/com/dataart/btle_android/helpers/PermissionsHelper.java @@ -0,0 +1,201 @@ +package com.dataart.btle_android.helpers; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; +import android.location.LocationManager; +import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.dataart.btle_android.R; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesUtil; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.location.LocationRequest; +import com.google.android.gms.location.LocationServices; +import com.google.android.gms.location.LocationSettingsRequest; +import com.google.android.gms.location.LocationSettingsResult; +import com.google.android.gms.location.LocationSettingsStatusCodes; + +import timber.log.Timber; + +/** + * Created by Constantine Mars on 12/6/15. + * Android M with Google Play Services requires Location enabled before starting BLE devices discovery + * This helper implements turning on Location services programmatically + */ +class PermissionsHelper implements ResultCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { + private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000; + private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2; + private static final int REQUEST_CHECK_SETTINGS = 100; + private LocationSettingsRequest mLocationSettingsRequest; + private GoogleApiClient mGoogleApiClient; + private LocationRequest mLocationRequest; + private LocationEnabledListener listener; + private Activity activity; + private boolean waitForResume = false; + + PermissionsHelper(LocationEnabledListener listener, Activity activity) { + this.listener = listener; + this.activity = activity; + buildGoogleApiClient(); + createLocationRequest(); + buildLocationSettingsRequest(); + } + + public void start() { + mGoogleApiClient.connect(); + } + + protected void stop() { + mGoogleApiClient.disconnect(); + } + + /** + * Build request to enable Location + */ + private void buildLocationSettingsRequest() { + LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); + builder.addLocationRequest(mLocationRequest); + mLocationSettingsRequest = builder.build(); + } + + private void buildGoogleApiClient() { + mGoogleApiClient = new GoogleApiClient.Builder(activity) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .addApi(LocationServices.API) + .build(); + } + + /** + * Location request is necessary for defining which exactly permissions must be enabled + */ + private void createLocationRequest() { + mLocationRequest = new LocationRequest(); + mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS); + mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS); + mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + } + + @Override + public void onResult(@NonNull LocationSettingsResult locationSettingsResult) { + final Status status = locationSettingsResult.getStatus(); + switch (status.getStatusCode()) { + case LocationSettingsStatusCodes.SUCCESS: + log(R.string.location_satisfied); + listener.onLocationEnabled(); + break; + case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: + log(R.string.location_unsatisfied); + + try { + // Show the dialog by calling startResolutionForResult(), and check the result in onActivityResult(). + status.startResolutionForResult(activity, REQUEST_CHECK_SETTINGS); + } catch (IntentSender.SendIntentException e) { + log(R.string.intent_not_started); + } + break; + case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: + log(R.string.location_cant_be_fixed); + break; + } + } + + @Override + public void onConnected(@Nullable Bundle bundle) { + } + + @Override + public void onConnectionSuspended(int i) { + } + + @Override + public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { + } + + void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + // Check for the integer request code originally supplied to startResolutionForResult(). + case REQUEST_CHECK_SETTINGS: + switch (resultCode) { + case Activity.RESULT_OK: + log(R.string.location_changes_applied); + listener.onLocationEnabled(); + break; + case Activity.RESULT_CANCELED: + log(R.string.loccation_changes_not_applied); + break; + } + break; + } + } + + private boolean isLocationEnabled() { + LocationManager lm = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE); + boolean gps_enabled = false; + boolean network_enabled = false; + + try { + gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER); + } catch (Exception ignored) { + } + + try { + network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + } catch (Exception ignored) { + } + + return gps_enabled || network_enabled; + } + + void checkLocationEnabled() { + if (isLocationEnabled()) { + listener.onLocationEnabled(); + return; + } + +// If location isn't enabled - check whether we can call Google Play Services or user to manually +// switch Location + + final int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity.getApplicationContext()); + if (status == ConnectionResult.SUCCESS) { + PendingResult result = + LocationServices.SettingsApi.checkLocationSettings( + mGoogleApiClient, + mLocationSettingsRequest + ); + result.setResultCallback(this); + } else { +// If no services available - the only thing we can do is to +// ask user to switch Location manually + activity.startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + waitForResume = true; + } + } + + void resume() { + if (waitForResume) { + checkLocationEnabled(); + } + } + + private String getString(int id) { + return activity.getString(id); + } + + private void log(int id) { + Timber.i(getString(id)); + } + + interface LocationEnabledListener { + void onLocationEnabled(); + } +} + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 7a1806b..f2155bf 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -21,8 +21,9 @@ + android:inputType="textUri" /> @@ -39,11 +40,11 @@ android:textAppearance="@android:style/TextAppearance.Medium" /> @@ -59,8 +60,8 @@ + style="@style/SingleLineEditText" + android:imeOptions="actionNext" /> @@ -82,25 +83,25 @@ android:id="@+id/save_button" android:layout_width="160dp" android:layout_height="wrap_content" - android:visibility="gone" - android:text="@string/button_restart" + android:layout_alignParentStart="true" android:layout_alignParentTop="true" - android:layout_alignParentStart="true" /> + android:text="@string/button_restart" + android:visibility="gone" /> + android:visibility="gone" /> \ No newline at end of file diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml index 821d7c0..c5f4aa5 100644 --- a/app/src/main/res/values-v21/styles.xml +++ b/app/src/main/res/values-v21/styles.xml @@ -1,5 +1,11 @@ - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..09a8320 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,9 @@ + + + #125688 + #125688 + #FFFFFF + #FFFFFF + #000000 + #c8e8ff + \ 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 d796123..631cdd5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -79,4 +79,7 @@ User chose not to make required location settings changes Unable to initialize BluetoothManager Unable to obtain a BluetoothAdapter + + Unsupported SDK version + Bluetooth LE is supported starting with SDK 18 (Android Jelly Bean MR2).\nPlease, update your Android version or try on another device diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c83c7e1..6ac7df0 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,8 +1,12 @@ - + + + - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 09a8320..e497ead 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,9 +1,5 @@ - #125688 - #125688 - #FFFFFF - #FFFFFF - #000000 - #c8e8ff + #000 + #444 \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f6bef7a..6f4afe3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -2,7 +2,7 @@