From a21bad0069a434a294063c3d9b147588cc1e7b92 Mon Sep 17 00:00:00 2001 From: dsame Date: Fri, 24 Nov 2023 09:23:54 +0100 Subject: [PATCH] v2.19 Fix problem with recent Android versions Migrate to Kalman filter for RSSI Migrate to WayToday and GPS SKDs --- app/build.gradle | 16 +- app/src/main/AndroidManifest.xml | 38 ++- app/src/main/java/s4y/itag/BootReceiver.java | 14 +- .../main/java/s4y/itag/ITagApplication.java | 46 ++- app/src/main/java/s4y/itag/ITagsFragment.java | 103 ++---- app/src/main/java/s4y/itag/ITagsService.java | 2 +- app/src/main/java/s4y/itag/MainActivity.java | 156 ++++++--- .../main/java/s4y/itag/MediaPlayerUtils.java | 7 + app/src/main/java/s4y/itag/RssiView.java | 25 +- app/src/main/java/s4y/itag/WaytodayView.java | 12 +- .../java/s4y/itag/history/HistoryRecord.java | 34 +- .../main/java/s4y/itag/waytoday/WayToday.java | 55 ++++ .../main/java/s4y/itag/waytoday/Waytoday.java | 44 --- .../drawable-v24/ic_launcher_background.xml | 10 + .../res/drawable/ic_launcher_foreground.xml | 298 ++++++++++++++++++ app/src/main/res/drawable/waytoday.xml | 256 +++++++++++++++ app/src/main/res/values-ar/strings.xml | 3 + app/src/main/res/values-es/strings.xml | 3 + app/src/main/res/values-fr/strings.xml | 3 + app/src/main/res/values-it/strings.xml | 3 + app/src/main/res/values-pt/strings.xml | 3 + app/src/main/res/values-ru/strings.xml | 3 + app/src/main/res/values/strings.xml | 6 +- build.gradle | 16 +- gradle/wrapper/gradle-wrapper.properties | 2 +- itagble/build.gradle | 13 +- itagble/src/main/AndroidManifest.xml | 6 +- .../itag/ble/BLECentralManagerDefault.java | 33 +- .../src/main/java/s4y/itag/ble/BLEError.java | 3 +- .../main/java/s4y/itag/ble/RSSIFilter.java | 47 +++ 30 files changed, 1009 insertions(+), 251 deletions(-) create mode 100644 app/src/main/java/s4y/itag/waytoday/WayToday.java delete mode 100644 app/src/main/java/s4y/itag/waytoday/Waytoday.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/waytoday.xml diff --git a/app/build.gradle b/app/build.gradle index 761e233..b13c4e2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,8 @@ -apply plugin: 'com.android.application' +plugins { + id 'com.android.application' + id 'com.google.firebase.crashlytics' + id 'com.google.gms.google-services' +} Properties properties = new Properties() if (project.rootProject.file('local.properties').exists()) { @@ -16,7 +20,7 @@ android { defaultConfig { applicationId "s4y.itag" - minSdkVersion 23 // due to mad-location-manager + minSdkVersion 23 targetSdkVersion 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField "String", "WAYTODAY_SECRET", "\"${System.getenv('WAYTODAY_SECRET') ?: properties.getProperty('waytoday.secret')}\"" @@ -58,7 +62,13 @@ dependencies { implementation project(':itagble') implementation "androidx.preference:preference:1.2.1" implementation "com.github.s4ysolutions:rasat-android:1.0.5" - implementation "com.github.s4ysolutions:WayTodaySDK-Android:1.0.36" + + implementation(platform("com.google.firebase:firebase-bom:33.1.0")) + implementation("com.google.firebase:firebase-crashlytics") + implementation("com.google.firebase:firebase-analytics") + + // implementation "solutions.s4y.gps:gps-sdk-android:1.0.0-dev.7" + implementation 'com.github.s4ysolutions:WayTodaySDK-Android:3.0.0-alpha1' /* testImplementation 'androidx.test.ext:junit:1.1.5' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 510bd12..57b3f6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,22 +2,28 @@ + android:versionCode="153" + android:versionName="2.19.0"> - - - - - - + + + + + + + + + + @@ -26,24 +32,26 @@ + android:theme="@style/Theme.AppCompat.Light"> - + - + @@ -58,9 +66,9 @@ + android:exported="false" + android:foregroundServiceType="connectedDevice" /> { if (context == null) { Log.e(LT, "Attempt to handle error before application created", th); - // FirebaseCrashlytics.getInstance().recordException(th); + FirebaseCrashlytics.getInstance().recordException(th); } else { Log.e(LT, "Toasted", th); if (toast) { Toast.makeText(context, th.getMessage(), Toast.LENGTH_LONG).show(); } - // FirebaseCrashlytics.getInstance().recordException(th); + FirebaseCrashlytics.getInstance().recordException(th); } }); } @@ -61,16 +66,17 @@ static public void handleError(@NonNull Throwable th) { handleError(th, BuildConfig.DEBUG); } - // private static FirebaseAnalytics sFirebaseAnalytics; + private static FirebaseAnalytics sFirebaseAnalytics; static public void fa(@NonNull final String event, Bundle bundle) { - if (context == null) return; - /* + if (context == null) { + FirebaseCrashlytics.getInstance().recordException(new IllegalStateException("Attempt to log event before the context assigned")); + return; + } if (sFirebaseAnalytics == null) { sFirebaseAnalytics = FirebaseAnalytics.getInstance(context); } sFirebaseAnalytics.logEvent(event, bundle); - */ if (BuildConfig.DEBUG) { Log.d(LT, "FA log " + event); } @@ -98,23 +104,19 @@ static public void faNotITag() { static public void faScanView(boolean empty) { Bundle bundle = new Bundle(); - /* bundle.putString(FirebaseAnalytics.Param.ITEM_ID, "itag_scan_view_is_empty"); bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, "First Scan"); bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, "boolean"); bundle.putBoolean(FirebaseAnalytics.Param.VALUE, empty); - */ fa("itag_scan_view", bundle); } static public void faITagsView(int devices) { Bundle bundle = new Bundle(); - /* bundle.putString(FirebaseAnalytics.Param.ITEM_ID, "itag_itags_view_device"); bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, "Remembered Devices"); bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, "int"); bundle.putInt(FirebaseAnalytics.Param.VALUE, devices); - */ fa("itag_itags_view"); } @@ -172,12 +174,10 @@ static public void faITagFindStopped() { static public void faITagLost(boolean error) { Bundle bundle = new Bundle(); - /* bundle.putString(FirebaseAnalytics.Param.ITEM_ID, "itag_itag_lost_error"); bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, "Lost with error"); bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, "boolean"); bundle.putBoolean(FirebaseAnalytics.Param.VALUE, error); - */ fa("itag_itag_lost"); } @@ -200,21 +200,27 @@ static public void faShowLastLocation() { static public void faIssuedGpsRequest() { fa("itag_issued_gps_request"); } + static public void faRemovedGpsRequestBySuccess() { fa("itag_removed_gps_request_by_success"); } + static public void faRemovedGpsRequestByConnect() { fa("itag_removed_gps_request_by_connect"); } + static public void faRemovedGpsRequestByTimeout() { fa("itag_removed_gps_request_by_timeout"); } + static public void faGotGpsLocation() { fa("itag_got_gps_location"); } + static public void faGpsPermissionError() { fa("itag_gps_permission_error"); } + static public void faDisconnectDuringCall() { fa("itag_disconnect_during_call"); } @@ -230,10 +236,15 @@ static public void faWtOn1() { @Override public void onCreate() { super.onCreate(); - faAppCreated(); context = this; + + faAppCreated(); ITag.initITag(context); - Waytoday.init(this); + + WayToday.init(context); + if (WayToday.getInstance().isTrackingOn() && !GPSPermissionManager.needPermissionRequest(this)) { + GPSUpdatesForegroundService.start(this); + } } static public void faWtNoTrackID() { @@ -242,7 +253,6 @@ static public void faWtNoTrackID() { @Override public void onTerminate() { - Waytoday.done(); try { ITag.close(); } catch (Exception e) { @@ -274,4 +284,8 @@ static public void faWtPlaymarket() { static public void faWtRemove() { fa("itag_wt_remove"); } + + static public void faWtRequestBlePermissions() { + fa("itag_ble_request_permissions"); + } } diff --git a/app/src/main/java/s4y/itag/ITagsFragment.java b/app/src/main/java/s4y/itag/ITagsFragment.java index 377792c..6016543 100644 --- a/app/src/main/java/s4y/itag/ITagsFragment.java +++ b/app/src/main/java/s4y/itag/ITagsFragment.java @@ -1,7 +1,6 @@ package s4y.itag; import android.app.Activity; -import android.content.Context; import android.os.Bundle; import android.os.Looper; import android.util.Log; @@ -28,20 +27,12 @@ import s4y.itag.itag.ITagInterface; import s4y.itag.preference.WayTodayDisabled0Preference; import s4y.itag.preference.VolumePreference; -import s4y.itag.waytoday.Waytoday; +import s4y.itag.waytoday.WayToday; import solutions.s4y.rasat.DisposableBag; -import solutions.s4y.waytoday.sdk.id.ITrackIDChangeListener; -import solutions.s4y.waytoday.sdk.id.TrackIDJobService; -import solutions.s4y.waytoday.sdk.tracker.ITrackingStateListener; -import solutions.s4y.waytoday.sdk.tracker.TrackerState; import static s4y.itag.itag.ITag.ble; -public class ITagsFragment extends Fragment - implements - HistoryRecord.HistoryRecordListener, - ITrackingStateListener, - ITrackIDChangeListener { +public class ITagsFragment extends Fragment implements HistoryRecord.HistoryRecordListener { private static final String LT = ITagsFragment.class.getName(); private Animation mLocationAnimation; private Animation mITagAnimation; @@ -109,7 +100,8 @@ private void setupTags(@NonNull ViewGroup root) { updateName(rootView, itag.name()); updateAlertButton(rootView, itag.isAlertDisconnected(), connection.isConnected()); } - updateRSSI(rootView, connection.rssi()); + int rssi = connection.state() == BLEConnectionState.connected ? connection.rssi() : -999; + updateRSSI(rootView, rssi); updateState(rootView, id, connection.state()); updateLocationImage(rootView, id); } @@ -154,10 +146,10 @@ private void updateWayToday() { Log.e(LT, "no waytoday imageview", new Exception("no waytoday imagegview")); return; } - if (Waytoday.tracker.isUpdating && TrackIDJobService.hasTid(getActivity())) { - String trackID = TrackIDJobService.getTid(getActivity()); + if (WayToday.getInstance().isTrackingOn() && WayToday.getInstance().wtClient.hasTrackerId()) { + String trackID = WayToday.getInstance().wtClient.getCurrentTrackerId(); if (BuildConfig.DEBUG) - Log.d(LT, "updateWayToday, updating=" + Waytoday.tracker.isUpdating + " trackID=" + trackID); + Log.d(LT, "updateWayToday, updating=" + WayToday.getInstance().isTrackingOn() + " trackID=" + trackID); waytoday.setVisibility(View.VISIBLE); TextView wtid = waytoday.findViewById(R.id.text_wt_id); wtid.setText(trackID); @@ -241,8 +233,7 @@ private void updateState(@NonNull ViewGroup rootView, @NonNull String id, @NonNu statusDrawableId = R.drawable.bt_setup; if (state == BLEConnectionState.connecting) statusTextId = R.string.bt_connecting; - else - statusTextId = R.string.bt_disconnecting; + else statusTextId = R.string.bt_disconnecting; } break; case writting: @@ -330,15 +321,9 @@ private void updateITagImageAnimation(@NonNull ViewGroup rootView, ITagInterface Animation animShake = null; if (BuildConfig.DEBUG) { - Log.d(LT, "updateITagImageAnimation isFindMe:" + connection.isFindMe() + - " isAlerting:" + connection.isAlerting() + - " isAlertDisconnected:" + itag.isAlertDisconnected() + - " not connected:" + !connection.isConnected() - ); - } - if (connection.isAlerting() || - connection.isFindMe() || - itag.isAlertDisconnected() && !connection.isConnected()) { + Log.d(LT, "updateITagImageAnimation isFindMe:" + connection.isFindMe() + " isAlerting:" + connection.isAlerting() + " isAlertDisconnected:" + itag.isAlertDisconnected() + " not connected:" + !connection.isConnected()); + } + if (connection.isAlerting() || connection.isFindMe() || itag.isAlertDisconnected() && !connection.isConnected()) { animShake = mITagAnimation;//AnimationUtils.loadAnimation(getActivity(), R.anim.shake_itag); } final ImageView imageITag = rootView.findViewById(R.id.image_itag); @@ -408,25 +393,18 @@ private void updateITagImage(@NonNull ViewGroup rootView, ITagInterface itag) { } @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment mLocationAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.shadow_location); mITagAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.shake_itag); - Context context = getContext(); final VolumePreference mute = new VolumePreference(getContext()); View root = inflater.inflate(R.layout.fragment_itags, container, false); if (root != null) { final ImageView imgMute = root.findViewById(R.id.btn_mute); int m = mute.get(); - imgMute.setImageResource( - m == VolumePreference.MUTE - ? R.drawable.mute - : m == VolumePreference.LOUD - ? R.drawable.nomute - : R.drawable.vibration); + imgMute.setImageResource(m == VolumePreference.MUTE ? R.drawable.mute : m == VolumePreference.LOUD ? R.drawable.nomute : R.drawable.vibration); imgMute.setOnClickListener(v -> { int volume = mute.get(); volume++; @@ -434,18 +412,9 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, volume = 0; } mute.set(volume); - imgMute.setImageResource( - volume == VolumePreference.MUTE - ? R.drawable.mute - : volume == VolumePreference.LOUD - ? R.drawable.nomute - : R.drawable.vibration); - - int toastId = volume == VolumePreference.MUTE - ? R.string.soundmode_off - : volume == VolumePreference.LOUD - ? R.string.soundmode_on - : R.string.soundmode_vibration; + imgMute.setImageResource(volume == VolumePreference.MUTE ? R.drawable.mute : volume == VolumePreference.LOUD ? R.drawable.nomute : R.drawable.vibration); + + int toastId = volume == VolumePreference.MUTE ? R.string.soundmode_off : volume == VolumePreference.LOUD ? R.string.soundmode_on : R.string.soundmode_vibration; Toast.makeText(getContext(), toastId, Toast.LENGTH_SHORT).show(); }); } @@ -493,8 +462,7 @@ public void onResume() { Log.d(LT, "onResume"); } Activity activity = getActivity(); - if (activity == null) - return; + if (activity == null) return; ITagApplication.faITagsView(ITag.store.count()); final ViewGroup root = (ViewGroup) requireView(); setupTags(root); @@ -533,8 +501,13 @@ public void onResume() { if (wt_disabled0) { root.findViewById(R.id.btn_waytoday).setVisibility(View.GONE); } else { - Waytoday.tracker.addOnTrackingStateListener(this); - TrackIDJobService.addOnTrackIDChangeListener(this); + disposableBag.add( + WayToday.getTrackIdObservable() + .subscribe(trackID -> activity.runOnUiThread(this::updateWayToday))); + disposableBag.add( + WayToday + .getGpsStatusObservable() + .subscribe(status -> getActivity().runOnUiThread(this::updateWayToday))); } startRssi(); } @@ -547,40 +520,14 @@ public void onPause() { stopRssi(); HistoryRecord.removeListener(this); disposableBag.dispose(); - Waytoday.tracker.removeOnTrackingStateListener(this); - TrackIDJobService.removeOnTrackIDChangeListener(this); super.onPause(); } @Override public void onHistoryRecordChange() { Activity activity = getActivity(); - if (activity == null) - return; + if (activity == null) return; Log.d(LT, "onHistoryRecordChange"); activity.runOnUiThread(this::updateLocationImages); } - - // ITrackingStateListener - @Override - public void onStateChange(@NonNull TrackerState state) { - final View view = getView(); - if (view != null) { - Activity activity = getActivity(); - if (activity == null) - return; - - activity.runOnUiThread(this::updateWayToday); - } - } - - // ITrackIDChangeListener - @Override - public void onTrackID(@NonNull String trackID) { - if (BuildConfig.DEBUG) Log.d(LT, "onTrackID: " + trackID); - Activity activity = getActivity(); - if (activity == null) - return; - activity.runOnUiThread(this::updateWayToday); - } } diff --git a/app/src/main/java/s4y/itag/ITagsService.java b/app/src/main/java/s4y/itag/ITagsService.java index 2361d4f..45e023e 100644 --- a/app/src/main/java/s4y/itag/ITagsService.java +++ b/app/src/main/java/s4y/itag/ITagsService.java @@ -127,7 +127,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } - public void putInForeground() { + private void putInForeground() { if (inForeground) { return; } diff --git a/app/src/main/java/s4y/itag/MainActivity.java b/app/src/main/java/s4y/itag/MainActivity.java index 5266b42..6d0c3d7 100644 --- a/app/src/main/java/s4y/itag/MainActivity.java +++ b/app/src/main/java/s4y/itag/MainActivity.java @@ -29,8 +29,13 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import java.util.ArrayList; import java.util.Locale; +import java.util.Objects; +import s4y.gps.sdk.android.GPSPermissionManager; +import s4y.gps.sdk.android.GPSPowerManager; +import s4y.gps.sdk.android.GPSUpdatesForegroundService; import s4y.itag.ble.AlertVolume; import s4y.itag.ble.BLEConnectionInterface; import s4y.itag.ble.BLEState; @@ -40,17 +45,12 @@ import s4y.itag.itag.TagColor; import s4y.itag.preference.WayTodayDisabled0Preference; import s4y.itag.preference.WayTodayFirstPreference; -import s4y.itag.waytoday.Waytoday; +import s4y.itag.waytoday.WayToday; import solutions.s4y.rasat.DisposableBag; -import solutions.s4y.waytoday.sdk.errors.ErrorsObservable; -import solutions.s4y.waytoday.sdk.id.TrackIDJobService; -import solutions.s4y.waytoday.sdk.locations.IPermissionListener; -import solutions.s4y.waytoday.sdk.permissionmanagement.PermissionHandling; -import solutions.s4y.waytoday.sdk.powermanagement.PowerManagement; public class MainActivity extends FragmentActivity { static public final int REQUEST_ENABLE_BT = 1; - static public final int REQUEST_ENABLE_LOCATION = 2; + static public final int REQUEST_ONSCAN = 2; public ITagsService iTagsService; public static boolean sIsShown = false; private static final String LT = MainActivity.class.getName(); @@ -63,6 +63,18 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (intent != null) { + String gpsNotification = intent.getStringExtra(GPSUpdatesForegroundService.GPS_SERVICE_NOTIFICATION); + if (Objects.equals(gpsNotification, GPSUpdatesForegroundService.ACTION_STOP_TRACKING)) { + WayToday.getInstance().turnTrackingOff(); + Toast.makeText(this, R.string.wt_tracking_stopped, Toast.LENGTH_LONG).show(); + } + } + } + private void setupProgressBar() { ProgressBar pb = findViewById(R.id.progress); if (ITag.ble.scanner().isScanning()) { @@ -99,20 +111,23 @@ public void onServiceDisconnected(ComponentName name) { private final ITagServiceConnection mServiceConnection = new ITagServiceConnection(); + /* + TODO: error handling private final ErrorsObservable.IErrorListener mErrorListener = errorNotification -> { runOnUiThread(() -> Toast.makeText(MainActivity.this, errorNotification.getMessage(), Toast.LENGTH_LONG).show()); Log.e(LT, errorNotification.getMessage(), errorNotification.th); }; - private final IPermissionListener gpsPermissionListener = () -> PermissionHandling.requestPermissions(MainActivity.this); + */ + // TODO: private final IPermissionListener gpsPermissionListener = () -> PermissionHandling.requestPermissions(MainActivity.this); private int resumeCount = 0; @Override protected void onResume() { super.onResume(); - ErrorsObservable.addErrorListener(mErrorListener); + // TODO: ErrorsObservable.addErrorListener(mErrorListener); sIsShown = true; setupContent(); - Waytoday.gpsLocationUpdater.addOnPermissionListener(gpsPermissionListener); + // TODO: Waytoday.gpsLocationUpdater.addOnPermissionListener(gpsPermissionListener); disposableBag.add(ITag.ble.observableState().subscribe(event -> setupContent())); disposableBag.add(ITag.ble.scanner().observableActive().subscribe( event -> { @@ -136,11 +151,18 @@ protected void onResume() { } })); bindService(ITagsService.intentBind(this), mServiceConnection, 0); - if (Waytoday.tracker.isOn(this) && PowerManagement.needRequestIgnoreOptimization(this)) { - if (resumeCount++ > 1) { + // unconditionally stop background service + if (WayToday.getInstance().isTrackingOn()) { + if (GPSPermissionManager.needPermissionRequest(this)) { new Handler(getMainLooper()).post(() -> - PowerManagement.requestIgnoreOptimization(this) + GPSPermissionManager.requestPermissions(this) ); + } else if (GPSPowerManager.needRequestIgnoreOptimization(this)) { + if (resumeCount++ > 1) { + new Handler(getMainLooper()).post(() -> + GPSPowerManager.requestIgnoreOptimization(this) + ); + } } } } @@ -154,13 +176,19 @@ protected void onPause() { } disposableBag.dispose(); sIsShown = false; - if (ITag.store.isDisconnectAlert() || Waytoday.tracker.isUpdating) { - ITagsService.start(this); - } else { - ITagsService.stop(this); + if (!exitting) { + if (ITag.store.isDisconnectAlert()) { + ITagsService.start(this); + } else { + ITagsService.stop(this); + } + + if (WayToday.getInstance().isTrackingOn() && !GPSPermissionManager.needPermissionRequest(this)) { + GPSUpdatesForegroundService.start(this); + } } - ErrorsObservable.removeErrorListener(mErrorListener); - Waytoday.gpsLocationUpdater.removePermissionListener(gpsPermissionListener); + + // TODO: Waytoday.gpsLocationUpdater.removePermissionListener(gpsPermissionListener); super.onPause(); } @@ -171,8 +199,11 @@ public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus != mHasFocus) { mHasFocus = hasFocus; - if (mHasFocus && iTagsService != null) { - iTagsService.removeFromForeground(); + if (mHasFocus) { + GPSUpdatesForegroundService.removeFromForeground(this); + if (iTagsService != null) { + iTagsService.removeFromForeground(); + } } } } @@ -383,12 +414,16 @@ public void onStartStopScan(View ignored) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.request_location_permission) .setTitle(R.string.request_permission_title) - .setPositiveButton(android.R.string.ok, (dialog, which) -> requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, MainActivity.REQUEST_ENABLE_LOCATION)) - .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel()) + .setPositiveButton(android.R.string.ok, + (dialog, which) -> + requestAllPermissions(MainActivity.REQUEST_ONSCAN)) + .setNegativeButton(android.R.string.cancel, + (dialog, which) -> + dialog.cancel()) .show(); } else { // isScanRequestAbortedBecauseOfPermission=true; - requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, MainActivity.REQUEST_ENABLE_LOCATION); + requestAllPermissions(MainActivity.REQUEST_ONSCAN); } return; } @@ -397,13 +432,31 @@ public void onStartStopScan(View ignored) { } } + private void requestAllPermissions(int code) { + ArrayList permissionsList = new ArrayList<>(); + permissionsList.add(Manifest.permission.ACCESS_FINE_LOCATION); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + permissionsList.add(Manifest.permission.BLUETOOTH_CONNECT); + permissionsList.add(Manifest.permission.BLUETOOTH_SCAN); + } + String[] permissions = permissionsList.toArray(new String[0]); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) + ITagApplication.faWtRequestBlePermissions(); + + requestPermissions(permissions, code); + } + + private Boolean exitting = false; + public void onAppMenu(@NonNull View sender) { final PopupMenu popupMenu = new PopupMenu(this, sender); popupMenu.inflate(R.menu.app); popupMenu.setOnMenuItemClickListener(item -> { if (item.getItemId() == R.id.exit) { + exitting = true; ITag.close(); - Waytoday.stop(); + WayToday.getInstance().gpsUpdatesManager.stop(); ITagsService.stop(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { finishAndRemoveTask(); @@ -419,48 +472,46 @@ public void onAppMenu(@NonNull View sender) { public void onWaytoday(@NonNull View sender) { boolean first = WayTodayFirstPreference.get(this); - String tid = TrackIDJobService.getTid(this); - boolean on = Waytoday.tracker.isOn(this); - int freq = Waytoday.tracker.frequencyMs(this); + String tid = WayToday.getInstance().wtClient.getCurrentTrackerId(); + boolean on = WayToday.getInstance().isTrackingOn() && !GPSPermissionManager.needPermissionRequest(this); + int freq = WayToday.getInstance().gpsUpdatesManager.getIntervalSec(); final PopupMenu popupMenu = new PopupMenu(this, sender); popupMenu.inflate(R.menu.waytoday); popupMenu.getMenu().findItem(R.id.wt_about_first).setVisible(first); popupMenu.getMenu().findItem(R.id.wt_sec_1).setChecked(on && freq == 1); - popupMenu.getMenu().findItem(R.id.wt_min_5).setChecked(on && freq == 300000); - popupMenu.getMenu().findItem(R.id.wt_hour_1).setChecked(on && freq == 3600000); + popupMenu.getMenu().findItem(R.id.wt_min_5).setChecked(on && freq == 300); + popupMenu.getMenu().findItem(R.id.wt_hour_1).setChecked(on && freq == 3600); popupMenu.getMenu().findItem(R.id.wt_off).setVisible(on); - popupMenu.getMenu().findItem(R.id.wt_new_tid).setVisible(!("".equals(tid))); + popupMenu.getMenu().findItem(R.id.wt_new_tid).setVisible(!(tid.isEmpty())); popupMenu.getMenu().findItem(R.id.wt_about).setVisible(!first); - popupMenu.getMenu().findItem(R.id.wt_share).setVisible(!"".equals(tid)); - popupMenu.getMenu().findItem(R.id.wt_map).setVisible(!"".equals(tid)); + popupMenu.getMenu().findItem(R.id.wt_share).setVisible(!tid.isEmpty()); + popupMenu.getMenu().findItem(R.id.wt_map).setVisible(!tid.isEmpty()); popupMenu.setOnMenuItemClickListener(item -> { AlertDialog.Builder builder; int id = item.getItemId(); if (id == R.id.wt_sec_1) { - Waytoday.tracker.setFrequencyMs(this, 1); - Waytoday.start(); + WayToday.getInstance().gpsUpdatesManager.setIntervalSec(1); ITagApplication.faWtOn1(); } else if (id == R.id.wt_min_5) { - Waytoday.tracker.setFrequencyMs(this, 5 * 60 * 1000); - Waytoday.start(); + WayToday.getInstance().gpsUpdatesManager.setIntervalSec(5 * 60); ITagApplication.faWtOn5(); } else if (id == R.id.wt_hour_1) { - Waytoday.tracker.setFrequencyMs(this, 60 * 60 * 1000); - Waytoday.start(); + WayToday.getInstance().gpsUpdatesManager.setIntervalSec(60 * 60); ITagApplication.faWtOn3600(); } else if (id == R.id.wt_off) { - Waytoday.stop(); + WayToday.getInstance().turnTrackingOff(); + WayToday.getInstance().gpsUpdatesManager.stop(); ITagApplication.faWtOff(); } else if (id == R.id.wt_new_tid) { ITagApplication.faWtChangeID(); - TrackIDJobService.enqueueRetrieveId(this); + WayToday.getInstance().enqueueTrackIdWorkRequest(this); } else if (id == R.id.wt_map) { - if (!"".equals(tid)) { + if (!tid.isEmpty()) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://way.today/#" + tid))); } } else if (id == R.id.wt_share) { ITagApplication.faWtShare(); - if (!"".equals(tid)) { + if (!tid.isEmpty()) { String txt = String.format(getResources().getString(R.string.share_link), tid); Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); @@ -510,6 +561,14 @@ public void onWaytoday(@NonNull View sender) { // Create the AlertDialog object and return it builder.create().show(); } + if (id == R.id.wt_sec_1 || id == R.id.wt_min_5 || id == R.id.wt_hour_1) { + if (GPSPermissionManager.needPermissionRequest(this)) { + WayToday.getInstance().enableTrackingOn(); + GPSPermissionManager.requestPermissions(this); + } else { + WayToday.getInstance().turnTrackingOn(); + } + } return true; }); popupMenu.show(); @@ -565,6 +624,9 @@ public void onDisconnectAlert(@NonNull View sender) { if (itag.isAlertDisconnected()) { Toast.makeText(this, R.string.mode_alertdisconnect, Toast.LENGTH_SHORT).show(); ITagApplication.faUnmuteTag(); + if (GPSPermissionManager.needPermissionRequest(this)) { + GPSPermissionManager.requestPermissions(this, getString(R.string.gps_permission_request)); + } } else { Toast.makeText(this, R.string.mode_keyfinder, Toast.LENGTH_SHORT).show(); ITagApplication.faMuteTag(); @@ -602,12 +664,16 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis case REQUEST_ENABLE_BT: setupContent(); break; - case REQUEST_ENABLE_LOCATION: + case REQUEST_ONSCAN: onStartStopScan(null); break; } } - PermissionHandling.handleOnRequestPermissionsResult(this, requestCode, Waytoday.tracker); + GPSPermissionManager + .handleOnRequestPermissionsResult( + requestCode, + WayToday.getInstance().gpsUpdatesManager, + WayToday.getInstance().isTrackingOn()); super.onRequestPermissionsResult(requestCode, permissions, grantResults); } diff --git a/app/src/main/java/s4y/itag/MediaPlayerUtils.java b/app/src/main/java/s4y/itag/MediaPlayerUtils.java index 3381ce4..d8ee466 100644 --- a/app/src/main/java/s4y/itag/MediaPlayerUtils.java +++ b/app/src/main/java/s4y/itag/MediaPlayerUtils.java @@ -9,6 +9,7 @@ import android.os.Looper; import android.os.VibrationEffect; import android.os.Vibrator; +import android.telephony.TelephonyManager; import java.io.IOException; @@ -109,6 +110,12 @@ public boolean isSound() { public void startSoundDisconnected(Context context) { stopSound(context); + // return if we are in a call + TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); + if(manager.getCallState() != TelephonyManager.CALL_STATE_IDLE){ + return; + } + AudioManager am = (AudioManager) context.getSystemService(AUDIO_SERVICE); if (am == null) diff --git a/app/src/main/java/s4y/itag/RssiView.java b/app/src/main/java/s4y/itag/RssiView.java index a061eef..bde1a0d 100644 --- a/app/src/main/java/s4y/itag/RssiView.java +++ b/app/src/main/java/s4y/itag/RssiView.java @@ -49,18 +49,23 @@ public RssiView(Context context, AttributeSet attrs, int defStyleAttr, int defSt static private final float BG_OFF=0.1f; public void setRssi(int level) { + // -999 indicates no signal + // theoretical min level -115 + // highly likely disconnection -90 l1.setAlpha(level > -115 ? BG_ON : BG_OFF); - l2.setAlpha(level > -109 ? BG_ON : BG_OFF); - l3.setAlpha(level > -103 ? BG_ON : BG_OFF); - l4.setAlpha(level > -97 ? BG_ON : BG_OFF); - l5.setAlpha(level > -93 ? BG_ON : BG_OFF); - l6.setAlpha(level > -89 ? BG_ON : BG_OFF); + l2.setAlpha(level > -92 ? BG_ON : BG_OFF); + l3.setAlpha(level > -90 ? BG_ON : BG_OFF); + l4.setAlpha(level > -89 ? BG_ON : BG_OFF); + l5.setAlpha(level > -87 ? BG_ON : BG_OFF); + l6.setAlpha(level > -86 ? BG_ON : BG_OFF); l7.setAlpha(level > -85 ? BG_ON : BG_OFF); - l8.setAlpha(level > -81 ? BG_ON : BG_OFF); - l9.setAlpha(level > -77 ? BG_ON : BG_OFF); - l10.setAlpha(level > -73 ? BG_ON : BG_OFF); - l11.setAlpha(level > -69 ? BG_ON : BG_OFF); - l12.setAlpha(level > -65 ? BG_ON : BG_OFF); + l8.setAlpha(level > -84 ? BG_ON : BG_OFF); + l9.setAlpha(level > -83? BG_ON : BG_OFF); + l10.setAlpha(level > -81 ? BG_ON : BG_OFF); + l11.setAlpha(level > -78 ? BG_ON : BG_OFF); + l12.setAlpha(level > -74 ? BG_ON : BG_OFF); // disconnection is highly likely + // real life nearest signal level -70 + // theoretical max level -65 } private void setup() { diff --git a/app/src/main/java/s4y/itag/WaytodayView.java b/app/src/main/java/s4y/itag/WaytodayView.java index 6a0d4de..fc09e69 100644 --- a/app/src/main/java/s4y/itag/WaytodayView.java +++ b/app/src/main/java/s4y/itag/WaytodayView.java @@ -1,10 +1,8 @@ package s4y.itag; -import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -13,8 +11,7 @@ import androidx.annotation.Nullable; -import s4y.itag.waytoday.Waytoday; -import solutions.s4y.waytoday.sdk.id.TrackIDJobService; +import s4y.itag.waytoday.WayToday; public class WaytodayView extends LinearLayout { public WaytodayView(Context context) { @@ -32,7 +29,6 @@ public WaytodayView(Context context, @Nullable AttributeSet attrs, int defStyleA setup(); } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) public WaytodayView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); setup(); @@ -45,8 +41,10 @@ private void setup() { root.setOnClickListener(v -> { ITagApplication.faWtVisit(); - if (TrackIDJobService.hasTid(getContext())) { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://way.today/#" + TrackIDJobService.getTid(getContext()))); + if (WayToday.getInstance().wtClient.hasTrackerId()) { + Intent browserIntent = new Intent( + Intent.ACTION_VIEW, + Uri.parse("https://way.today/#" + WayToday.getInstance().wtClient.getCurrentTrackerId())); getContext().startActivity(browserIntent); } }); diff --git a/app/src/main/java/s4y/itag/history/HistoryRecord.java b/app/src/main/java/s4y/itag/history/HistoryRecord.java index 3eb9e66..0de8c6f 100644 --- a/app/src/main/java/s4y/itag/history/HistoryRecord.java +++ b/app/src/main/java/s4y/itag/history/HistoryRecord.java @@ -1,18 +1,29 @@ package s4y.itag.history; +import android.Manifest; import android.content.Context; +import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.Looper; import android.util.Log; +import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; + +import com.google.android.gms.location.CurrentLocationRequest; +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.Granularity; +import com.google.android.gms.location.LocationServices; +import com.google.android.gms.location.Priority; +import com.google.android.gms.tasks.CancellationTokenSource; +import com.google.android.gms.tasks.Task; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; @@ -23,6 +34,9 @@ import java.util.List; import java.util.Map; +import s4y.gps.sdk.GPSCurrentPositionManager; +import s4y.gps.sdk.android.GPSPermissionManager; +import s4y.gps.sdk.android.implementation.FusedGPSCurrentPositionProvider; import s4y.itag.BuildConfig; import s4y.itag.ITagApplication; import s4y.itag.R; @@ -137,7 +151,13 @@ public static void add(final Context context, String id) { if (locationManager == null) return; - boolean isGPSEnabled = locationManager + boolean gpsPermitted = ActivityCompat + .checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED + && + ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) + != PackageManager.PERMISSION_GRANTED; + boolean isGPSEnabled = gpsPermitted && locationManager .isProviderEnabled(LocationManager.GPS_PROVIDER); boolean isNetworkEnabled = locationManager @@ -152,14 +172,14 @@ public static void add(final Context context, String id) { .getLastKnownLocation(LocationManager.GPS_PROVIDER); if (location != null) { if (BuildConfig.DEBUG) { - if (location == null) { - Log.d(LT, "can't getLastKnownLocation from GPS. id:" + id); - } else { - Log.d(LT, "getLastKnownLocation from GPS in " + (System.currentTimeMillis() - location.getTime()) / 1000 + "sec. id:" + id); - } + Log.d(LT, "getLastKnownLocation from GPS in " + (System.currentTimeMillis() - location.getTime()) / 1000 + "sec. id:" + id); } add(context, new HistoryRecord(id, location, System.currentTimeMillis())); gotBestLocation = System.currentTimeMillis() - location.getTime() > 30000; + } else { + if (BuildConfig.DEBUG) { + Log.d(LT, "can't getLastKnownLocation from GPS. id:" + id); + } } } catch (SecurityException e) { ITagApplication.handleError(e); diff --git a/app/src/main/java/s4y/itag/waytoday/WayToday.java b/app/src/main/java/s4y/itag/waytoday/WayToday.java new file mode 100644 index 0000000..e3a5818 --- /dev/null +++ b/app/src/main/java/s4y/itag/waytoday/WayToday.java @@ -0,0 +1,55 @@ +package s4y.itag.waytoday; + +import android.content.Context; +import android.os.Build; + +import kotlin.Unit; +import s4y.gps.sdk.android.GPSUpdatesForegroundService; +import s4y.gps.sdk.dependencies.IGPSUpdatesProvider; +import solutions.s4y.rasat.Channel; +import solutions.s4y.rasat.Observable; +import solutions.s4y.waytoday.sdk.AndroidWayTodayClient; +public class WayToday { + private static AndroidWayTodayClient instance; + private static Channel trackIdChannel = new Channel<>(); + private static Channel gpsStatusChannel = new Channel(); + + public static void init(Context context) { + if (instance != null) { + throw new IllegalStateException("WayToday is already initialized"); + } + + instance = new AndroidWayTodayClient(context, "iTagAndroid", "secret", "iTagAndroid"); + instance.wtClient.addTrackIdChangeListener(tid -> { + trackIdChannel.broadcast(tid); + }); + + instance.gpsUpdatesManager.getStatus().addListener(status -> { + gpsStatusChannel.broadcast(status); + // TODO: ugly + return Unit.INSTANCE; + }); + + GPSUpdatesForegroundService.setUpdatesManager(instance.gpsUpdatesManager); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + GPSUpdatesForegroundService.setNotificationChannelId("itag_gps_updates"); + GPSUpdatesForegroundService.setNotificationChannelName("ITag GPS Updates"); + GPSUpdatesForegroundService.setNotificationId(22); + GPSUpdatesForegroundService.setNotificationContentTitle("Stop iTag WayToday tracking"); + } + } + public static AndroidWayTodayClient getInstance() { + if (instance == null) { + throw new IllegalStateException("WayToday is not initialized, call init() first"); + } + return instance; + } + + public static Observable getTrackIdObservable() { + return trackIdChannel.observable; + } + + public static Observable getGpsStatusObservable() { + return gpsStatusChannel.observable; + } +} diff --git a/app/src/main/java/s4y/itag/waytoday/Waytoday.java b/app/src/main/java/s4y/itag/waytoday/Waytoday.java deleted file mode 100644 index bfd7487..0000000 --- a/app/src/main/java/s4y/itag/waytoday/Waytoday.java +++ /dev/null @@ -1,44 +0,0 @@ -package s4y.itag.waytoday; - -import android.app.Application; -import s4y.itag.BuildConfig; -import s4y.itag.ITagApplication; -import solutions.s4y.waytoday.sdk.locations.LocationGPSUpdater; -import solutions.s4y.waytoday.sdk.tracker.Tracker; - -public class Waytoday { - - public static LocationGPSUpdater gpsLocationUpdater; - public static final Tracker tracker = new Tracker(BuildConfig.WAYTODAY_SECRET, "iTagAndroid"); - private static boolean initialized = false; - private static Application context; - - public static void init(Application context) { - initialized = true; - Waytoday.context = context; - - gpsLocationUpdater = new LocationGPSUpdater(context); - gpsLocationUpdater.addOnPermissionListener(() -> { - - }); - if (tracker.isOn(context)) { - start(); - } - } - - public static void done() { - tracker.stop(context); - } - - public static void start() { - if (!initialized) { - init(ITagApplication.application); - } - tracker.requestStart(ITagApplication.context, gpsLocationUpdater); - } - - public static void stop() { - tracker.stop(context); - tracker.resetFilter(); - } -} diff --git a/app/src/main/res/drawable-v24/ic_launcher_background.xml b/app/src/main/res/drawable-v24/ic_launcher_background.xml new file mode 100644 index 0000000..23a3e92 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..28a2df0 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/waytoday.xml b/app/src/main/res/drawable/waytoday.xml new file mode 100644 index 0000000..155b636 --- /dev/null +++ b/app/src/main/res/drawable/waytoday.xml @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 3cbf230..3c6ae5d 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -92,4 +92,7 @@ وضع الباحث عن المفاتيح وضع التنبيه المفقود + توقف تتبع WayToday + غير قادر على الحصول على الموقع الحالي بسبب عدم وجود أذونات الموقع + مطلوب الإذن لتحديد الموقع الحالي في حالة فقدان الاتصال \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c5e03ba..a27d00b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -90,4 +90,7 @@ Sin alarma cuando iTag perdió Modo buscador de claves Modo de alarma perdido + Seguimiento de WayToday detenido + Incapaz de obtener la posición actual debido a la falta de permisos de ubicación + Se requiere permiso para determinar la ubicación actual en caso de pérdida de conexión \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f56161c..97c0be7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -90,4 +90,7 @@ Pas d\'alarme quand iTag a perdu Mode de recherche de clé Mode d\'alarme perdu + Arrêt du suivi WayToday + Impossible d\'obtenir la position actuelle en raison de l\'absence d\'autorisations de localisation + Autorisation requise pour déterminer l\'emplacement actuel en cas de perte de connexion \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 7130347..ea895fa 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -90,4 +90,7 @@ Nessun allarme quando iTag ha perso Modalità ricerca chiavi Modalità di allarme perso + WayToday tracking fermato + Impossibile ottenere la posizione corrente a causa della mancanza di autorizzazioni per la localizzazione + È richiesto il permesso per determinare la posizione attuale in caso di perdita di connessione \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 2b8c9c9..9fb2d6b 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -90,4 +90,7 @@ Modo localizador de chave Modo de alarme perdido iTag One + Rastreamento WayToday parado + Incapaz de obter a posição atual devido à falta de permissões de localização + É necessário permissão para determinar a localização atual em caso de perda de conexão \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 223324a..6631cbd 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -92,4 +92,7 @@ Только поиск брелка Сигнализировать при утере брелка + Отслеживание WayToday остановлено + Невозможно получить текущее положение из-за отсутствия разрешений на местоположение + Требуется разрешение для определения текущего местоположения в случае потери связи \ 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 06a77d7..707d109 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -79,7 +79,7 @@ min hr - Stop tracking + Exit 1 hour Half an hour @@ -113,4 +113,8 @@ Key finder mode Lost alarm mode + + WayToday tracking stopped + Unable to get the current position due to lack of location permissions + Permission required to determine current location in case of connection loss diff --git a/build.gradle b/build.gradle index 9c60b84..7e1d658 100644 --- a/build.gradle +++ b/build.gradle @@ -1,20 +1,26 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { - + + ext { + kotlin_version = '2.0.0' + } repositories { gradlePluginPortal() google() mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:8.1.2" + classpath 'com.android.tools.build:gradle:8.4.1' + classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.1' + classpath 'com.google.gms:google-services:4.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } + } allprojects { repositories { google() + mavenLocal() mavenCentral() maven { url 'https://jitpack.io' } } @@ -24,4 +30,4 @@ allprojects { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" } } -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index da1db5f..17655d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/itagble/build.gradle b/itagble/build.gradle index eff94f4..a20d865 100644 --- a/itagble/build.gradle +++ b/itagble/build.gradle @@ -1,4 +1,6 @@ -apply plugin: 'com.android.library' +plugins { + id 'com.android.library' +} android { namespace = 's4y.itag.ble' @@ -8,8 +10,6 @@ android { defaultConfig { minSdkVersion 23 // due to mad-location-manager targetSdkVersion 34 - versionCode 1 - versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' @@ -37,5 +37,10 @@ dependencies { // implementation 'androidx.appcompat:appcompat:1.2.0' // implementation 'solutions.s4y.rasat:rasat-android:1.0.3' implementation "com.github.s4ysolutions:rasat-android:1.0.5" - implementation 'androidx.annotation:annotation:1.7.0' + implementation 'androidx.annotation:annotation:1.8.0' + implementation 'org.apache.commons:commons-math3:3.6.1' + + implementation(platform("com.google.firebase:firebase-bom:33.1.0")) + implementation("com.google.firebase:firebase-crashlytics") + implementation("com.google.firebase:firebase-analytics") } diff --git a/itagble/src/main/AndroidManifest.xml b/itagble/src/main/AndroidManifest.xml index 54d5c12..3a4cd78 100644 --- a/itagble/src/main/AndroidManifest.xml +++ b/itagble/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ - - + + + + diff --git a/itagble/src/main/java/s4y/itag/ble/BLECentralManagerDefault.java b/itagble/src/main/java/s4y/itag/ble/BLECentralManagerDefault.java index 00b092c..e897954 100644 --- a/itagble/src/main/java/s4y/itag/ble/BLECentralManagerDefault.java +++ b/itagble/src/main/java/s4y/itag/ble/BLECentralManagerDefault.java @@ -1,11 +1,9 @@ package s4y.itag.ble; -import android.Manifest; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; @@ -18,6 +16,8 @@ import static android.bluetooth.BluetoothProfile.GATT; import static android.bluetooth.BluetoothProfile.STATE_CONNECTED; +import com.google.firebase.crashlytics.FirebaseCrashlytics; + class BLECentralManagerDefault implements BLECentralManagerInterface, AutoCloseable { private static final String L = BLECentralManagerDefault.class.getName(); private final Context context; @@ -93,7 +93,12 @@ public BLEError enable() { if (adapter == null) { return BLEError.noAdapter; } - adapter.enable(); + try { + adapter.enable(); + } catch (SecurityException e) { + FirebaseCrashlytics.getInstance().recordException(e); + return BLEError.noPermission; + } return BLEError.ok; } @@ -116,8 +121,12 @@ public void startScan() { } if (adapter != null) { if (!isScanning(adapter)) { - adapter.startLeScan(leScanCallback); - isScanning = true; + try { + adapter.startLeScan(leScanCallback); + isScanning = true; + }catch (SecurityException e) { + FirebaseCrashlytics.getInstance().recordException(e); + } } } } @@ -131,6 +140,8 @@ public void stopScan() { if (isScanning(adapter)) { try { adapter.stopLeScan(leScanCallback); + } catch (SecurityException exception) { + FirebaseCrashlytics.getInstance().recordException(exception); }catch (NullPointerException ignored) { } @@ -148,9 +159,15 @@ public boolean connected(BluetoothDevice device) { BluetoothManager bluetoothManager = getManager(); if (bluetoothManager == null) return false; - int state = bluetoothManager.getConnectionState(device, GATT); - return state == STATE_CONNECTED; - // List devices = bluetoothManager.getConnectedDevices(BluetoothProfile.STATE_CONNECTED); + try { + + int state = bluetoothManager.getConnectionState(device, GATT); + return state == STATE_CONNECTED; + // List devices = bluetoothManager.getConnectedDevices(BluetoothProfile.STATE_CONNECTED); + } catch (SecurityException e) { + FirebaseCrashlytics.getInstance().recordException(e); + return false; + } } @Override diff --git a/itagble/src/main/java/s4y/itag/ble/BLEError.java b/itagble/src/main/java/s4y/itag/ble/BLEError.java index 16d7ff4..44e4714 100644 --- a/itagble/src/main/java/s4y/itag/ble/BLEError.java +++ b/itagble/src/main/java/s4y/itag/ble/BLEError.java @@ -9,5 +9,6 @@ public enum BLEError { notConnected, noImmediateAlertCharacteristic, noFindMeAlertCharacteristic, - badStatus + badStatus, + noPermission, } diff --git a/itagble/src/main/java/s4y/itag/ble/RSSIFilter.java b/itagble/src/main/java/s4y/itag/ble/RSSIFilter.java index 7eb8c45..b691c48 100644 --- a/itagble/src/main/java/s4y/itag/ble/RSSIFilter.java +++ b/itagble/src/main/java/s4y/itag/ble/RSSIFilter.java @@ -1,8 +1,54 @@ package s4y.itag.ble; +import org.apache.commons.math3.filter.DefaultMeasurementModel; +import org.apache.commons.math3.filter.DefaultProcessModel; +import org.apache.commons.math3.filter.KalmanFilter; +import org.apache.commons.math3.filter.MeasurementModel; +import org.apache.commons.math3.filter.ProcessModel; +import org.apache.commons.math3.linear.Array2DRowRealMatrix; +import org.apache.commons.math3.linear.ArrayRealVector; +import org.apache.commons.math3.linear.RealMatrix; + import java.util.Arrays; public class RSSIFilter { + private KalmanFilter filter; + + public RSSIFilter() { + reset(); + } + + public void reset() { + // A = [ 1 ] constant rssi + RealMatrix A = new Array2DRowRealMatrix(new double[]{1d}); +// no control input + RealMatrix B = null; +// H = [ 1 ] + RealMatrix H = new Array2DRowRealMatrix(new double[]{1d}); +// Q = [ 0 ] process noise + // 1 indicator level ~ 1 dBm + RealMatrix Q = new Array2DRowRealMatrix(new double[]{3 * 3}); +// R = [ 0 ] measurement noise + // RSSI varies from -65 to -115 dBm = 50 dBm + // assume the measurement is within that range + RealMatrix R = new Array2DRowRealMatrix(new double[]{25 * 25}); + // initial state -85 dBm - empirical ~1-2m + ProcessModel pm + = new DefaultProcessModel(A, B, Q, new ArrayRealVector(new double[]{85}), null); + MeasurementModel mm = new DefaultMeasurementModel(H, R); + + filter = new KalmanFilter(pm, mm); + } + + public void add(int rawmeasurement) { + filter.predict(); + filter.correct(new double[]{-rawmeasurement}); + } + + public int get() { + return - (int) filter.getStateEstimation()[0]; + } + /* final int[] measurements = {0, 0, 0, 0, 0, 0, 0}; final int[] deviations = {0, 0, 0, 0, 0, 0, 0}; final int[] spikes = {0, 0, 0}; @@ -55,4 +101,5 @@ public void add(int rawmeasurement) { public int get() { return average; } + */ }