diff --git a/.idea/copyright/GPLv3.xml b/.idea/copyright/GPLv3.xml new file mode 100644 index 00000000..bb6cb37e --- /dev/null +++ b/.idea/copyright/GPLv3.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..3b363ccf --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..86a0dbfb --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Florent Revest revestflo@gmail.com +Doug Koellmer dougkoellmer@hotmail.com +Justus Tartz git@jrtberlin.de +Doomsdayrs doomsdayrs@gmail.com \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4e5d1f06..2da65c64 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("com.android.application") + kotlin("android") } android { @@ -35,8 +36,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } lint { checkReleaseBuilds = true @@ -53,11 +54,14 @@ repositories { dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + implementation("androidx.core:core-ktx:1.9.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") testImplementation("junit:junit:4.13.2") - implementation("androidx.appcompat:appcompat:1.5.1") + implementation("androidx.appcompat:appcompat:1.6.0") implementation("androidx.legacy:legacy-support-v4:1.0.0") implementation("androidx.cardview:cardview:1.0.0") - implementation("com.google.android.material:material:1.6.1") + implementation("com.google.android.material:material:1.8.0") implementation("com.github.MagneFire:EasyWeather:1.3") implementation("com.google.code.gson:gson:2.9.1") implementation("org.osmdroid:osmdroid-android:6.1.14") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 328e9e04..026a17b3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,8 @@ + + diff --git a/app/src/main/java/org/asteroidos/sync/MainActivity.java b/app/src/main/java/org/asteroidos/sync/MainActivity.java index 84487647..e5bc8ffe 100644 --- a/app/src/main/java/org/asteroidos/sync/MainActivity.java +++ b/app/src/main/java/org/asteroidos/sync/MainActivity.java @@ -1,5 +1,25 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync; +import static android.os.ParcelUuid.fromString; + import android.Manifest; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; @@ -17,10 +37,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.Message; -import android.os.Messenger; import android.os.ParcelUuid; -import android.os.RemoteException; import android.provider.Settings; import android.util.Log; import android.view.MenuItem; @@ -35,6 +52,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; import org.asteroidos.sync.asteroid.IAsteroidDevice; import org.asteroidos.sync.fragments.AppListFragment; @@ -45,6 +63,8 @@ import org.asteroidos.sync.utils.AppInfo; import org.asteroidos.sync.utils.AppInfoHelper; import org.asteroidos.sync.utils.AsteroidUUIDS; +import org.asteroidos.sync.viewmodel.base.MainActivityViewModel; +import org.asteroidos.sync.viewmodel.impl.MainActivityViewModelImpl; import java.util.ArrayList; import java.util.List; @@ -55,8 +75,6 @@ import no.nordicsemi.android.support.v18.scanner.ScanResult; import no.nordicsemi.android.support.v18.scanner.ScanSettings; -import static android.os.ParcelUuid.fromString; - public class MainActivity extends AppCompatActivity implements DeviceListFragment.OnDefaultDeviceSelectedListener, DeviceListFragment.OnScanRequestedListener, DeviceDetailFragment.OnDefaultDeviceUnselectedListener, DeviceDetailFragment.OnConnectRequestedListener, DeviceDetailFragment.OnAppSettingsClickedListener, @@ -67,21 +85,17 @@ public class MainActivity extends AppCompatActivity implements DeviceListFragmen public static final String PREFS_DEFAULT_LOC_NAME = "defaultLocalName"; private static final String TAG = "MainActivity"; public static ArrayList appInfoList; - final Messenger mDeviceDetailMessenger = new Messenger(new MainActivity.SynchronizationHandler(this)); - public ParcelUuid asteroidUUID = fromString(AsteroidUUIDS.SERVICE_UUID.toString()); - Messenger mSyncServiceMessenger; + public final ParcelUuid asteroidUUID = fromString(AsteroidUUIDS.SERVICE_UUID.toString()); ActivityResultLauncher mLocationEnableActivityLauncher; LocationManager mLocationManager; /* Synchronization service events handling */ private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { - mSyncServiceMessenger = new Messenger(service); onUpdateRequested(); } public void onServiceDisconnected(ComponentName className) { - mSyncServiceMessenger = null; } }; Intent mSyncServiceIntent; @@ -89,7 +103,7 @@ public void onServiceDisconnected(ComponentName className) { ScanSettings mSettings; List mFilters; private DeviceListFragment mListFragment; - public ScanCallback scanCallback = new ScanCallback() { + public final ScanCallback scanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, @NonNull ScanResult result) { super.onScanResult(callbackType, result); @@ -102,7 +116,7 @@ public void onScanResult(int callbackType, @NonNull ScanResult result) { } return; } - Log.d(TAG, "SCAN RESULT:" + result.getDevice().toString() + " Name:" + result.getDevice().getName()); + Log.d(TAG, "SCAN RESULT:" + result.getDevice() + " Name:" + result.getDevice().getName()); ParcelUuid[] arr = result.getDevice().getUuids(); } }; @@ -110,22 +124,23 @@ public void onScanResult(int callbackType, @NonNull ScanResult result) { private Fragment mPreviousFragment; private BluetoothLeScannerCompat mScanner; private SharedPreferences mPrefs; + MainActivityViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + viewModel = new ViewModelProvider(this).get(MainActivityViewModelImpl.class); + viewModel.onWatchLocalNameChanged(this::handleSetLocalName); + viewModel.onWatchConnectionStateChanged(this::handleSetStatus); + viewModel.onWatchBatteryPercentageChanged(this::handleSetBatteryPercentage); mScanner = BluetoothLeScannerCompat.getScanner(); mPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); String defaultDevMacAddr = mPrefs.getString(PREFS_DEFAULT_MAC_ADDR, ""); - Thread appInfoRetrieval = new Thread(new Runnable() { - public void run() { - appInfoList = AppInfoHelper.getPackageInfo(MainActivity.this); - } - }); + Thread appInfoRetrieval = new Thread(() -> appInfoList = AppInfoHelper.getPackageInfo(MainActivity.this)); appInfoRetrieval.start(); mSettings = new ScanSettings.Builder() @@ -179,20 +194,14 @@ public void onDefaultDeviceSelected(BluetoothDevice mDevice) { mDetailFragment = new DeviceDetailFragment(); if (mListFragment != null) mListFragment.scanningStopped(); - else if (mDetailFragment != null) mDetailFragment.scanningStopped(); + else mDetailFragment.scanningStopped(); getSupportFragmentManager() .beginTransaction() .replace(R.id.flContainer, mDetailFragment) .commit(); - try { - Message msg = Message.obtain(null, SynchronizationService.MSG_SET_DEVICE); - msg.obj = mDevice; - msg.replyTo = mDeviceDetailMessenger; - mSyncServiceMessenger.send(msg); - } catch (RemoteException ignored) { - } + viewModel.onDefaultDeviceSelected(mDevice); onConnectRequested(); @@ -209,13 +218,7 @@ public void onDefaultDeviceUnselected() { .replace(R.id.flContainer, mListFragment) .commit(); - try { - Message msg = Message.obtain(null, SynchronizationService.MSG_UNSET_DEVICE); - msg.obj = ""; - msg.replyTo = mDeviceDetailMessenger; - mSyncServiceMessenger.send(msg); - } catch (RemoteException ignored) { - } + viewModel.requestUnsetDevice(); mDetailFragment = null; setTitle(R.string.app_name); @@ -223,35 +226,19 @@ public void onDefaultDeviceUnselected() { @Override public void onUpdateRequested() { - try { - Message msg = Message.obtain(null, SynchronizationService.MSG_UPDATE); - msg.replyTo = mDeviceDetailMessenger; - if (mSyncServiceMessenger != null) - mSyncServiceMessenger.send(msg); - } catch (RemoteException ignored) { - } + viewModel.requestUpdate(); } @Override public void onConnectRequested() { if (mScanner != null) mScanner.stopScan(scanCallback); - try { - Message msg = Message.obtain(null, SynchronizationService.MSG_CONNECT); - msg.replyTo = mDeviceDetailMessenger; - mSyncServiceMessenger.send(msg); - } catch (RemoteException ignored) { - } + viewModel.requestConnect(); } @Override public void onDisconnectRequested() { - try { - Message msg = Message.obtain(null, SynchronizationService.MSG_DISCONNECT); - msg.replyTo = mDeviceDetailMessenger; - mSyncServiceMessenger.send(msg); - } catch (RemoteException ignored) { - } + viewModel.requestDisconnect(); } @Override @@ -342,12 +329,7 @@ private void handleSetStatus(IAsteroidDevice.ConnectionState status) { mStatus = status; } if (status == IAsteroidDevice.ConnectionState.STATUS_CONNECTED) { - try { - Message msg = Message.obtain(null, SynchronizationService.MSG_REQUEST_BATTERY_LIFE); - msg.replyTo = mDeviceDetailMessenger; - mSyncServiceMessenger.send(msg); - } catch (RemoteException ignored) { - } + viewModel.requestBatteryLevel(); } } @@ -418,7 +400,7 @@ protected void onPause() { } @Override - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); getDelegate().onConfigurationChanged(newConfig); int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; @@ -435,29 +417,4 @@ public void onConfigurationChanged(Configuration newConfig) { overridePendingTransition(0, 0); startActivity(getIntent()); } - - static private class SynchronizationHandler extends Handler { - private final MainActivity mActivity; - - SynchronizationHandler(MainActivity activity) { - mActivity = activity; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case SynchronizationService.MSG_SET_LOCAL_NAME: - mActivity.handleSetLocalName((String) msg.obj); - break; - case SynchronizationService.MSG_SET_STATUS: - mActivity.handleSetStatus((IAsteroidDevice.ConnectionState) msg.obj); - break; - case SynchronizationService.MSG_SET_BATTERY_PERCENTAGE: - mActivity.handleSetBatteryPercentage(msg.arg1); - break; - default: - super.handleMessage(msg); - } - } - } } diff --git a/app/src/main/java/org/asteroidos/sync/NotificationPreferences.java b/app/src/main/java/org/asteroidos/sync/NotificationPreferences.java index 5112801a..503fa00b 100644 --- a/app/src/main/java/org/asteroidos/sync/NotificationPreferences.java +++ b/app/src/main/java/org/asteroidos/sync/NotificationPreferences.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync; import android.content.Context; @@ -21,7 +39,7 @@ public enum NotificationOption { STRONG_VIBRATION(4), RINGTONE_VIBRATION(5); - private int value; + private final int value; NotificationOption(int value) { this.value = value; } diff --git a/app/src/main/java/org/asteroidos/sync/PermissionsActivity.java b/app/src/main/java/org/asteroidos/sync/PermissionsActivity.java index 9c4ae711..54472e70 100644 --- a/app/src/main/java/org/asteroidos/sync/PermissionsActivity.java +++ b/app/src/main/java/org/asteroidos/sync/PermissionsActivity.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync; import android.Manifest; @@ -16,11 +34,14 @@ import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import org.asteroidos.sync.services.NLService; +import java.util.ArrayList; + import io.github.dreierf.materialintroscreen.MaterialIntroActivity; import io.github.dreierf.materialintroscreen.SlideFragment; import io.github.dreierf.materialintroscreen.SlideFragmentBuilder; @@ -41,105 +62,116 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { if (hasBLE) { if (!isLowRamDevice) { - SlideFragment welcomeFragment = new SlideFragmentBuilder() - .backgroundColor(R.color.colorintroslide1) - .buttonsColor(R.color.colorintroslide1button) - .image(R.drawable.introslide1icon) - .title(getString(R.string.intro_slide1_title)) - .description(getString(R.string.intro_slide1_subtitle)) - .build(); - - SlideFragment externalStorageFragment = new SlideFragmentBuilder() - .backgroundColor(R.color.colorintroslide2) - .buttonsColor(R.color.colorintroslide2button) - .neededPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}) - .image(R.drawable.introslide2icon) - .title(getString(R.string.intro_slide2_title)) - .description(getString(R.string.intro_slide2_subtitle)) - .build(); + SlideFragment welcomeFragment = new SlideFragmentBuilder() + .backgroundColor(R.color.colorintroslide1) + .buttonsColor(R.color.colorintroslide1button) + .image(R.drawable.introslide1icon) + .title(getString(R.string.intro_slide1_title)) + .description(getString(R.string.intro_slide1_subtitle)) + .build(); + + SlideFragment externalStorageFragment = new SlideFragmentBuilder() + .backgroundColor(R.color.colorintroslide2) + .buttonsColor(R.color.colorintroslide2button) + .neededPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}) + .image(R.drawable.introslide2icon) + .title(getString(R.string.intro_slide2_title)) + .description(getString(R.string.intro_slide2_subtitle)) + .build(); boolean externalStorageFragmentShown = false; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - externalStorageFragmentShown = (ContextCompat.checkSelfPermission(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED); - } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + externalStorageFragmentShown = (ContextCompat.checkSelfPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED); + } - SlideFragmentBuilder localizationFragmentBuilder = new SlideFragmentBuilder() - .backgroundColor(R.color.colorintroslide3) - .buttonsColor(R.color.colorintroslide3button) - .neededPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}) - .image(R.drawable.introslide3icon) - .title(getString(R.string.intro_slide3_title)) - .description(getString(R.string.intro_slide3_subtitle)); + ArrayList permissions = new ArrayList<>(); + permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - localizationFragmentBuilder.neededPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN}); - } - SlideFragment localizationFragment = localizationFragmentBuilder.build(); - - boolean localizationFragmentShown = (ContextCompat.checkSelfPermission(this, - Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - localizationFragmentShown |= (ContextCompat.checkSelfPermission(this, - Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED); - localizationFragmentShown |= (ContextCompat.checkSelfPermission(this, - Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED); - } + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + permissions.add(Manifest.permission.BLUETOOTH_CONNECT); + permissions.add(Manifest.permission.BLUETOOTH_SCAN); + } - boolean notificationsPermissionFragmentShown = false; - SlideFragment notificationsPermissionFragment = null; - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - SlideFragmentBuilder notificationsPermissionSlideBuilder = new SlideFragmentBuilder() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){ + permissions.add(Manifest.permission.NEARBY_WIFI_DEVICES); + } + + SlideFragmentBuilder localizationFragmentBuilder = new SlideFragmentBuilder() .backgroundColor(R.color.colorintroslide3) .buttonsColor(R.color.colorintroslide3button) - .neededPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}) - .image(R.drawable.ic_notifications) - .title(getString(R.string.intro_slide6_title)) - .description(getString(R.string.intro_slide6_subtitle)); - notificationsPermissionSlideBuilder.neededPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}); - notificationsPermissionFragment = notificationsPermissionSlideBuilder.build(); - notificationsPermissionFragmentShown = (ContextCompat.checkSelfPermission(this, - Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED); - } + .neededPermissions(permissions.toArray(new String[]{})) + .image(R.drawable.introslide3icon) + .title(getString(R.string.intro_slide3_title)) + .description(getString(R.string.intro_slide3_subtitle)); - NotificationsSlide notificationFragment = new NotificationsSlide(); - notificationFragment.setContext(this); - boolean notificationFragmentShown = notificationFragment.hasAnyPermissionsToGrant(); - - BatteryOptimSlide batteryOptimFragment = new BatteryOptimSlide(); - batteryOptimFragment.setContext(this); - boolean batteryOptimFragmentShown = batteryOptimFragment.hasAnyPermissionsToGrant(); - - SlideFragment phoneStateFragment = new SlideFragmentBuilder() - .backgroundColor(R.color.colorintroslide2) - .buttonsColor(R.color.colorintroslide2button) - .neededPermissions(new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_CALL_LOG, Manifest.permission.READ_CONTACTS}) - .image(R.drawable.ic_ring_volume) - .title(getString(R.string.intro_phonestateslide_title)) - .description(getString(R.string.intro_phonestateslide_subtitle)) - .build(); - boolean phoneStateFragmentShown = (ContextCompat.checkSelfPermission(this, - Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) || - (ContextCompat.checkSelfPermission(this, - Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) || - (ContextCompat.checkSelfPermission(this, - Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED); - - if (externalStorageFragmentShown || localizationFragmentShown || - notificationFragmentShown || notificationsPermissionFragmentShown || batteryOptimFragmentShown || phoneStateFragmentShown) { - addSlide(welcomeFragment); - if (externalStorageFragmentShown) addSlide(externalStorageFragment); - if (localizationFragmentShown) addSlide(localizationFragment); - if (notificationsPermissionFragmentShown && notificationsPermissionFragment != null) addSlide(notificationsPermissionFragment); - if (notificationFragmentShown) addSlide(notificationFragment); - if (batteryOptimFragmentShown) addSlide(batteryOptimFragment); - if (phoneStateFragmentShown) addSlide(phoneStateFragment); - } else - startMainActivity(); - } else { + + SlideFragment localizationFragment = localizationFragmentBuilder.build(); + + boolean localizationFragmentShown = (ContextCompat.checkSelfPermission(this, + Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + localizationFragmentShown |= (ContextCompat.checkSelfPermission(this, + Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED); + localizationFragmentShown |= (ContextCompat.checkSelfPermission(this, + Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED); + } + + boolean notificationsPermissionFragmentShown = false; + SlideFragment notificationsPermissionFragment = null; + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + SlideFragmentBuilder notificationsPermissionSlideBuilder = new SlideFragmentBuilder() + .backgroundColor(R.color.colorintroslide3) + .buttonsColor(R.color.colorintroslide3button) + .neededPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}) + .image(R.drawable.ic_notifications) + .title(getString(R.string.intro_slide6_title)) + .description(getString(R.string.intro_slide6_subtitle)); + notificationsPermissionSlideBuilder.neededPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}); + notificationsPermissionFragment = notificationsPermissionSlideBuilder.build(); + notificationsPermissionFragmentShown = (ContextCompat.checkSelfPermission(this, + Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED); + } + + NotificationsSlide notificationFragment = new NotificationsSlide(); + notificationFragment.setContext(this); + boolean notificationFragmentShown = notificationFragment.hasAnyPermissionsToGrant(); + + BatteryOptimSlide batteryOptimFragment = new BatteryOptimSlide(); + batteryOptimFragment.setContext(this); + boolean batteryOptimFragmentShown = batteryOptimFragment.hasAnyPermissionsToGrant(); + + SlideFragment phoneStateFragment = new SlideFragmentBuilder() + .backgroundColor(R.color.colorintroslide2) + .buttonsColor(R.color.colorintroslide2button) + .neededPermissions(new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_CALL_LOG, Manifest.permission.READ_CONTACTS}) + .image(R.drawable.ic_ring_volume) + .title(getString(R.string.intro_phonestateslide_title)) + .description(getString(R.string.intro_phonestateslide_subtitle)) + .build(); + boolean phoneStateFragmentShown = (ContextCompat.checkSelfPermission(this, + Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) || + (ContextCompat.checkSelfPermission(this, + Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) || + (ContextCompat.checkSelfPermission(this, + Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED); + + if (externalStorageFragmentShown || localizationFragmentShown || + notificationFragmentShown || notificationsPermissionFragmentShown || batteryOptimFragmentShown || phoneStateFragmentShown) { + addSlide(welcomeFragment); + if (externalStorageFragmentShown) addSlide(externalStorageFragment); + if (localizationFragmentShown) addSlide(localizationFragment); + if (notificationsPermissionFragmentShown && notificationsPermissionFragment != null) + addSlide(notificationsPermissionFragment); + if (notificationFragmentShown) addSlide(notificationFragment); + if (batteryOptimFragmentShown) addSlide(batteryOptimFragment); + if (phoneStateFragmentShown) addSlide(phoneStateFragment); + } else + startMainActivity(); + } else { addSlide(new AndroidGoSlide()); } } else { - addSlide(new BLENotSupportedSlide()); + addSlide(new BLENotSupportedSlide()); } } @@ -169,7 +201,7 @@ static public class NotificationsSlide extends SlideFragment { @Nullable @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Bundle bundle = new Bundle(); bundle.putInt("background_color", R.color.colorintroslide4); bundle.putInt("buttons_color", R.color.colorintroslide4button); @@ -209,7 +241,7 @@ static public class BatteryOptimSlide extends SlideFragment { @Nullable @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Bundle bundle = new Bundle(); bundle.putInt("background_color", R.color.colorintroslide5); bundle.putInt("buttons_color", R.color.colorintroslide5button); @@ -227,13 +259,10 @@ public void setContext(Context ctx) { @Override public boolean hasAnyPermissionsToGrant() { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - String packageName = mCtx.getPackageName(); - PowerManager pm = (PowerManager) mCtx.getSystemService(POWER_SERVICE); - return (pm != null && !pm.isIgnoringBatteryOptimizations(packageName)); - } + String packageName = mCtx.getPackageName(); + PowerManager pm = (PowerManager) mCtx.getSystemService(POWER_SERVICE); + return (pm != null && !pm.isIgnoringBatteryOptimizations(packageName)); - return false; } // Ignore violation of play store content since @@ -241,15 +270,12 @@ public boolean hasAnyPermissionsToGrant() { @SuppressLint("BatteryLife") @Override public void askForPermissions() { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Intent intent = new Intent(); - String packageName = mCtx.getPackageName(); - PowerManager pm = (PowerManager) mCtx.getSystemService(POWER_SERVICE); - intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + packageName)); - getActivity().startActivityForResult(intent, BATTERYOPTIM_REQUEST); - - } + Intent intent = new Intent(); + String packageName = mCtx.getPackageName(); + PowerManager pm = (PowerManager) mCtx.getSystemService(POWER_SERVICE); + intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.parse("package:" + packageName)); + getActivity().startActivityForResult(intent, BATTERYOPTIM_REQUEST); } @Override diff --git a/app/src/main/java/org/asteroidos/sync/adapters/AppInfoAdapter.java b/app/src/main/java/org/asteroidos/sync/adapters/AppInfoAdapter.java index 16cf096a..85277576 100644 --- a/app/src/main/java/org/asteroidos/sync/adapters/AppInfoAdapter.java +++ b/app/src/main/java/org/asteroidos/sync/adapters/AppInfoAdapter.java @@ -1,5 +1,6 @@ package org.asteroidos.sync.adapters; +// TODO Handle dubious copyright // copied from https://github.com/jensstein/oandbackup, used under MIT license import android.content.Context; @@ -30,9 +31,10 @@ public class AppInfoAdapter extends ArrayAdapter { private final static String TAG = AppInfoAdapter.class.getSimpleName(); - private Context context; - private ArrayList items; - private int iconSize, layout; + private final Context context; + private final ArrayList items; + private int iconSize; + private final int layout; public AppInfoAdapter(Context context, int layout, ArrayList items) { super(context, layout, items); @@ -134,8 +136,8 @@ private static class ViewHolder { Spinner spinner; } - abstract class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener { - String packageName; + abstract static class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener { + final String packageName; MyOnItemSelectedListener(String packageName) { this.packageName = packageName; @@ -144,7 +146,7 @@ abstract class MyOnItemSelectedListener implements AdapterView.OnItemSelectedLis private class SeenPackagesFilter extends Filter { - private List seenPackages; + private final List seenPackages; private SeenPackagesFilter(List seenPackages) { this.seenPackages = seenPackages; diff --git a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java index 1ac0a8c2..07550758 100644 --- a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java +++ b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java @@ -1,16 +1,31 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.asteroid; -import android.Manifest; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.content.Context; -import android.content.pm.PackageManager; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; import org.asteroidos.sync.connectivity.IConnectivityService; import org.asteroidos.sync.connectivity.IServiceCallback; @@ -30,9 +45,9 @@ public class AsteroidBleManager extends BleManager { public static final String TAG = AsteroidBleManager.class.toString(); @Nullable public BluetoothGattCharacteristic batteryCharacteristic; - SynchronizationService mSynchronizationService; - ArrayList mGattServices; - public HashMap recvCallbacks; + final SynchronizationService mSynchronizationService; + final ArrayList mGattServices; + public final HashMap recvCallbacks; public HashMap sendingCharacteristics; public AsteroidBleManager(@NonNull final Context context, SynchronizationService syncService) { @@ -64,11 +79,6 @@ public final void abort() { cancelQueue(); } - @Override - protected final void finalize() throws Throwable { - super.finalize(); - } - public final void setBatteryLevel(Data data) { BatteryLevelEvent batteryLevelEvent = new BatteryLevelEvent(); batteryLevelEvent.battery = Objects.requireNonNull(data.getByte(0)).intValue(); @@ -91,7 +101,6 @@ the characteristics in the isRequiredServiceSupported() function. */ public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) { final BluetoothGattService batteryService = gatt.getService(AsteroidUUIDS.BATTERY_SERVICE_UUID); - boolean supported = true; boolean notify = false; if (batteryService != null) { @@ -127,8 +136,7 @@ public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gat }); } - supported = (batteryCharacteristic != null && notify); - return supported; + return (batteryCharacteristic != null && notify); } @Override @@ -142,20 +150,20 @@ protected final void initialize() { .enqueue(); setNotificationCallback(batteryCharacteristic).with(((device, data) -> setBatteryLevel(data))); - // Do not call readCharacteristic(batteryCharacteristic) here. - // Otherwise, on Android 12 and later, the BLE bond to the watch - // is lost, and communication no longer works. Arguably, reading - // and writing should not be done in initialize(), since it is - // part of the BLE manager's connect() request. Instead, do those - // IO operations _after_ that request finishes (-> read the - // characteristic in the SynchronizationService class, which is - // where the BLE manager's connect() function is called). + // Do not call readCharacteristic(batteryCharacteristic) here. + // Otherwise, on Android 12 and later, the BLE bond to the watch + // is lost, and communication no longer works. Arguably, reading + // and writing should not be done in initialize(), since it is + // part of the BLE manager's connect() request. Instead, do those + // IO operations _after_ that request finishes (-> read the + // characteristic in the SynchronizationService class, which is + // where the BLE manager's connect() function is called). enableNotifications(batteryCharacteristic).enqueue(); // Let all services know that we are connected. try { mSynchronizationService.syncServices(); - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } } diff --git a/app/src/main/java/org/asteroidos/sync/asteroid/IAsteroidDevice.java b/app/src/main/java/org/asteroidos/sync/asteroid/IAsteroidDevice.java index 8645fcc2..3fe92ccf 100644 --- a/app/src/main/java/org/asteroidos/sync/asteroid/IAsteroidDevice.java +++ b/app/src/main/java/org/asteroidos/sync/asteroid/IAsteroidDevice.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.asteroid; import org.asteroidos.sync.connectivity.IConnectivityService; diff --git a/app/src/main/java/org/asteroidos/sync/common/ext/Log.kt b/app/src/main/java/org/asteroidos/sync/common/ext/Log.kt new file mode 100644 index 00000000..0f3187cd --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/common/ext/Log.kt @@ -0,0 +1,113 @@ +/* + * This file is part of shosetsu & asteroidOS (imported to) + * + * shosetsu is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * shosetsu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with shosetsu. If not, see . + * + * 14 / 08 / 2020 + */ +package org.asteroidos.sync.common.ext + +import android.util.Log.* +import java.io.PrintStream + +const val NULL_METHOD_NAME = "UnknownMethod" + +var fileOut: PrintStream? = null + +const val CRESET: String = "\u001B[0m" +const val CRED: String = "\u001B[31m" + +fun writeT(t: Throwable? = null) { + if (t != null) + fileOut?.println(t.stackTraceToString()) +} + +@Suppress("unused") +inline fun T.logI(message: String?, t: Throwable? = null): Int { + val name = Thread.currentThread().stackTrace[2].methodName ?: NULL_METHOD_NAME + val msg = "${name}:\t$message" + val tag = T::class.java.simpleName + + fileOut?.println("i:\t$tag:\t$msg") + + writeT(t) + + return i(tag, msg, t) +} + +@Suppress("unused") +inline fun T.logD(message: String?, t: Throwable? = null): Int { + val name = Thread.currentThread().stackTrace[2].methodName ?: NULL_METHOD_NAME + val msg = "${name}:\t$message" + val tag = T::class.java.simpleName + + fileOut?.println("D:\t$tag:\t$msg") + + writeT(t) + + return d(tag, msg, t) +} + +@Suppress("unused") +inline fun T.logE(message: String?, t: Throwable? = null): Int { + val name = Thread.currentThread().stackTrace[2].methodName ?: NULL_METHOD_NAME + val msg = "${name}:\t$message" + val tag = T::class.java.simpleName + + fileOut?.println("${CRED}e:\t$tag:\t$msg${CRESET}") + + writeT(t) + + return e(tag, msg, t) +} + +@Suppress("unused") +inline fun T.logW(message: String?, t: Throwable? = null): Int { + val name = Thread.currentThread().stackTrace[2].methodName ?: NULL_METHOD_NAME + val msg = "${name}:\t$message" + val tag = T::class.java.simpleName + + fileOut?.println("w:\t$tag:\t$msg") + + writeT(t) + + return w(tag, msg, t) +} + +@Suppress("unused") +inline fun T.logV(message: String?, t: Throwable? = null): Int { + val name = Thread.currentThread().stackTrace[2].methodName ?: NULL_METHOD_NAME + val msg = "${name}:\t$message" + val tag = T::class.java.simpleName + + fileOut?.println("v:\t$tag:\t$msg") + + writeT(t) + + return v(tag, msg, t) +} + +@Suppress("unused") +inline fun T.logWTF(message: String?, t: Throwable? = null): Int { + val name = Thread.currentThread().stackTrace[2].methodName ?: NULL_METHOD_NAME + val msg = "${name}:\t$message" + val tag = T::class.java.simpleName + + fileOut?.println("wtf:\t$tag:\t$msg") + + writeT(t) + + return wtf(tag, msg, t) +} + diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/IConnectivityService.java b/app/src/main/java/org/asteroidos/sync/connectivity/IConnectivityService.java index 9b04436d..728b43d5 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/IConnectivityService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/IConnectivityService.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.connectivity; import java.util.HashMap; diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/IService.java b/app/src/main/java/org/asteroidos/sync/connectivity/IService.java index ab989555..03461ab6 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/IService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/IService.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.connectivity; /** @@ -9,6 +27,6 @@ * ({@link IService#unsync()}) is called when the watch is disconnected. */ public interface IService { - public void sync(); - public void unsync(); + void sync(); + void unsync(); } diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/IServiceCallback.java b/app/src/main/java/org/asteroidos/sync/connectivity/IServiceCallback.java index 7bd698ce..7d76ec4c 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/IServiceCallback.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/IServiceCallback.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.connectivity; public interface IServiceCallback { diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java b/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java index 787784b7..c50dab0b 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2016 - Florent Revest + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java b/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java index 18013759..1dcc503c 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2016 - Florent Revest + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,9 +35,9 @@ public class NotificationService implements IConnectivityService { public static final String TAG = NotificationService.class.toString(); - private Context mCtx; - private IAsteroidDevice mDevice; - private NotificationReceiver mNReceiver; + private final Context mCtx; + private final IAsteroidDevice mDevice; + private final NotificationReceiver mNReceiver; public NotificationService(Context ctx, IAsteroidDevice device) { this.mDevice = device; diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/ScreenshotService.java b/app/src/main/java/org/asteroidos/sync/connectivity/ScreenshotService.java index 90e2d3ac..7cc10748 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/ScreenshotService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/ScreenshotService.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2016 - Florent Revest + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +18,7 @@ package org.asteroidos.sync.connectivity; +import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -54,13 +56,12 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -@SuppressWarnings({"FieldCanBeLocal"}) // For clarity, we prefer having NOTIFICATION as a top level field public class ScreenshotService implements IConnectivityService { private static final String NOTIFICATION_CHANNEL_ID = "screenshotservice_channel_id_01"; - private int NOTIFICATION = 2726; + private final int NOTIFICATION = 2726; - private Context mCtx; - private IAsteroidDevice mDevice; + private final Context mCtx; + private final IAsteroidDevice mDevice; private ScreenshotReqReceiver mSReceiver; @@ -71,7 +72,7 @@ public class ScreenshotService implements IConnectivityService { private byte[] totalData; private ScheduledExecutorService processUpdate; - private NotificationManager mNM; + private final NotificationManager mNM; public ScreenshotService(Context ctx, IAsteroidDevice device) { mDevice = device; @@ -194,14 +195,14 @@ private Uri createFile(byte[] totalData) throws IOException { metaInfo.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()); Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI , metaInfo); assert imageUri != null; + @SuppressLint("Recycle") OutputStream out = resolver.openOutputStream(imageUri); - assert out != null; - try { + try (out) { + assert out != null; out.write(totalData); } catch (IOException ioe) { ioe.printStackTrace(); } finally { - out.close(); uri = imageUri; } } else { @@ -251,7 +252,6 @@ public void onReceive(Context context, Intent intent) { mFirstNotify = true; mDownloading = true; byte[] data = new byte[1]; - data[0] = 0x0; mDevice.send(AsteroidUUIDS.SCREENSHOT_REQUEST, data, ScreenshotService.this); } } diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/SilentModeService.java b/app/src/main/java/org/asteroidos/sync/connectivity/SilentModeService.java index 4ba93ad9..d06a5bb6 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/SilentModeService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/SilentModeService.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2019 - Justus Tartz + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,16 +28,14 @@ public class SilentModeService implements SharedPreferences.OnSharedPreferenceCh public static final String PREFS_NAME = "AppPreferences"; public static final String PREF_RINGER = "PhoneRingModeOnConnection"; private static final String PREF_ORIG_RINGER = "OriginalRingMode"; - private SharedPreferences prefs; + private final SharedPreferences prefs; private Boolean notificationPref; - private Context context; - private AudioManager am; + private final AudioManager am; public SilentModeService(Context con) { prefs = con.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); - context = con; prefs.registerOnSharedPreferenceChangeListener(this); - am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + am = (AudioManager) con.getSystemService(Context.AUDIO_SERVICE); } diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/TimeService.java b/app/src/main/java/org/asteroidos/sync/connectivity/TimeService.java index 8254f800..38f3558c 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/TimeService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/TimeService.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2016 - Florent Revest + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,10 +42,10 @@ public class TimeService implements IConnectivityService, SharedPreferences.OnSh public static final boolean PREFS_SYNC_TIME_DEFAULT = true; public static final String TIME_SYNC_INTENT = "org.asteroidos.sync.TIME_SYNC_REQUEST_LISTENER"; - private IAsteroidDevice mDevice; - private Context mCtx; + private final IAsteroidDevice mDevice; + private final Context mCtx; - private SharedPreferences mTimeSyncSettings; + private final SharedPreferences mTimeSyncSettings; private TimeSyncReqReceiver mSReceiver; private PendingIntent alarmPendingIntent; @@ -60,12 +61,7 @@ public TimeService(Context ctx, IAsteroidDevice device) { @Override public final void sync() { Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - updateTime(); - } - }, 500); + handler.postDelayed(this::updateTime, 500); // Register a broadcast handler to use for the alarm Intent // Also listen for TIME_CHANGED and TIMEZONE_CHANGED events diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/WeatherService.java b/app/src/main/java/org/asteroidos/sync/connectivity/WeatherService.java index 115ffc92..733a067a 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/WeatherService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/WeatherService.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2016 - Florent Revest + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -61,9 +62,9 @@ public class WeatherService implements IConnectivityService { public static final boolean PREFS_SYNC_WEATHER_DEFAULT = false; public static final String WEATHER_SYNC_INTENT = "org.asteroidos.sync.WEATHER_SYNC_REQUEST_LISTENER"; - private IAsteroidDevice mDevice; - private Context mCtx; - private SharedPreferences mSettings; + private final IAsteroidDevice mDevice; + private final Context mCtx; + private final SharedPreferences mSettings; private WeatherSyncReqReceiver mSReceiver; private PendingIntent mAlarmPendingIntent; @@ -132,12 +133,7 @@ private void updateWeather() { // We don't have a valid Location yet // Use the old location until we have a new one, recheck in 2 Minutes Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - updateWeather(); - } - }, 1000 * 60 * 2); + handler.postDelayed(this::updateWeather, 1000 * 60 * 2); return; } } diff --git a/app/src/main/java/org/asteroidos/sync/dataobjects/Notification.java b/app/src/main/java/org/asteroidos/sync/dataobjects/Notification.java index 8dec8636..0b38f517 100644 --- a/app/src/main/java/org/asteroidos/sync/dataobjects/Notification.java +++ b/app/src/main/java/org/asteroidos/sync/dataobjects/Notification.java @@ -1,11 +1,29 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.dataobjects; import java.nio.charset.StandardCharsets; public class Notification { String packageName, appName, appIcon, summary, body, vibration = ""; - MsgType msgType = null; - int id = 0; + final MsgType msgType; + final int id; public Notification(MsgType msgType, String packageName, int id, String appName, String appIcon, String summary, String body, String vibration) { this.msgType = msgType; diff --git a/app/src/main/java/org/asteroidos/sync/domain/repository/base/MessageRepository.kt b/app/src/main/java/org/asteroidos/sync/domain/repository/base/MessageRepository.kt new file mode 100644 index 00000000..f2942a70 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/domain/repository/base/MessageRepository.kt @@ -0,0 +1,82 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.domain.repository.base + +import android.bluetooth.BluetoothDevice +import kotlinx.coroutines.flow.Flow +import org.asteroidos.sync.domain.repository.impl.MessageRepositoryImpl +import org.asteroidos.sync.domain.repository.impl.WatchStatusRepositoryImpl + +/** + * Source of truth for all inter app message communication using flows. + */ +interface MessageRepository { + /** + * Emits when a device is selected + */ + val requestSetDeviceFlow: Flow + + fun requestSetDevice(device: BluetoothDevice) + + /** + * Emits when device should be unset + */ + val requestUnsetDeviceFlow: Flow + + fun requestUnsetDevice() + + /** + * Emits when a device update is requested + */ + val requestUpdateDeviceFlow: Flow + + fun requestUpdateDevice() + + /** + * Emits when a device should be connected to + */ + val requestConnectFlow: Flow + + fun requestConnect() + + /** + * Emits when a device should be disconnected from + */ + val requestDisconnectFlow: Flow + + fun requestDisconnect() + + /** + * Emits when a battery life update is requested + */ + val requestBatteryLifeFlow: Flow + + fun requestBatteryLife() + + companion object { + private var repo: MessageRepository? = null + + fun get(): MessageRepository { + if (repo == null) + repo = MessageRepositoryImpl() + + return repo!! + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/domain/repository/base/WatchStatusRepository.kt b/app/src/main/java/org/asteroidos/sync/domain/repository/base/WatchStatusRepository.kt new file mode 100644 index 00000000..18821a6a --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/domain/repository/base/WatchStatusRepository.kt @@ -0,0 +1,60 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.domain.repository.base + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import org.asteroidos.sync.asteroid.IAsteroidDevice +import org.asteroidos.sync.asteroid.IAsteroidDevice.ConnectionState +import org.asteroidos.sync.domain.repository.impl.WatchStatusRepositoryImpl + +/** + * Source of truth on watch status + */ +interface WatchStatusRepository { + /** + * Battery percentage state + */ + val batteryPercentage: StateFlow + + fun setBatteryPercentage(batteryPercentage: Int) + + /** + * Connection state + */ + val connectionState: StateFlow + fun setConnectionState(state: ConnectionState) + + /** + * Device name + */ + val deviceName: StateFlow + fun setDeviceName(name: String) + + companion object { + private var repo: WatchStatusRepository? = null + + fun get(): WatchStatusRepository { + if (repo == null) + repo = WatchStatusRepositoryImpl() + + return repo!! + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/domain/repository/impl/MessageRepositoryImpl.kt b/app/src/main/java/org/asteroidos/sync/domain/repository/impl/MessageRepositoryImpl.kt new file mode 100644 index 00000000..a9232830 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/domain/repository/impl/MessageRepositoryImpl.kt @@ -0,0 +1,64 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.domain.repository.impl + +import android.bluetooth.BluetoothDevice +import kotlinx.coroutines.flow.* +import org.asteroidos.sync.domain.repository.base.MessageRepository + +class MessageRepositoryImpl : MessageRepository { + private val _requestSetDeviceFlow = MutableStateFlow(null) + + override val requestSetDeviceFlow: Flow = + _requestSetDeviceFlow.filterNotNull() + + override fun requestSetDevice(device: BluetoothDevice) { + _requestSetDeviceFlow.value = device + } + + override val requestUnsetDeviceFlow: MutableStateFlow = MutableStateFlow(0) + + override fun requestUnsetDevice() { + requestUnsetDeviceFlow.value = requestUnsetDeviceFlow.value++ + } + + override val requestUpdateDeviceFlow: MutableStateFlow = MutableStateFlow(0) + + override fun requestUpdateDevice() { + requestUpdateDeviceFlow.value = requestUpdateDeviceFlow.value++ + } + + override val requestConnectFlow: MutableStateFlow = MutableStateFlow(0) + + override fun requestConnect() { + requestConnectFlow.value = requestConnectFlow.value++ + } + + override val requestDisconnectFlow: MutableStateFlow = MutableStateFlow(0) + + override fun requestDisconnect() { + requestDisconnectFlow.value = requestDisconnectFlow.value++ + } + + override val requestBatteryLifeFlow: MutableStateFlow = MutableStateFlow(0) + + override fun requestBatteryLife() { + requestBatteryLifeFlow.value = requestBatteryLifeFlow.value++ + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/domain/repository/impl/WatchStatusRepositoryImpl.kt b/app/src/main/java/org/asteroidos/sync/domain/repository/impl/WatchStatusRepositoryImpl.kt new file mode 100644 index 00000000..2c75f372 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/domain/repository/impl/WatchStatusRepositoryImpl.kt @@ -0,0 +1,44 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.domain.repository.impl + +import kotlinx.coroutines.flow.MutableStateFlow +import org.asteroidos.sync.asteroid.IAsteroidDevice.ConnectionState +import org.asteroidos.sync.domain.repository.base.WatchStatusRepository + +class WatchStatusRepositoryImpl : WatchStatusRepository { + + override val batteryPercentage: MutableStateFlow = MutableStateFlow(0) + + override fun setBatteryPercentage(batteryPercentage: Int) { + this.batteryPercentage.value = batteryPercentage + } + + override val connectionState = MutableStateFlow(ConnectionState.STATUS_DISCONNECTED) + + override fun setConnectionState(state: ConnectionState) { + connectionState.value = state + } + + override val deviceName = MutableStateFlow("") + + override fun setDeviceName(name: String) { + deviceName.value = name + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/fragments/AppListFragment.java b/app/src/main/java/org/asteroidos/sync/fragments/AppListFragment.java index f5356f6f..ee6a5b8c 100644 --- a/app/src/main/java/org/asteroidos/sync/fragments/AppListFragment.java +++ b/app/src/main/java/org/asteroidos/sync/fragments/AppListFragment.java @@ -1,13 +1,32 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.fragments; import android.content.Context; import android.os.Bundle; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Filter; import android.widget.ListView; import org.asteroidos.sync.MainActivity; @@ -20,7 +39,7 @@ public class AppListFragment extends Fragment { View placeholder; @Override - public void onAttach(Context context) { + public void onAttach(@NonNull Context context) { super.onAttach(context); adapter = new AppInfoAdapter(context, R.layout.app_list_item, MainActivity.appInfoList); adapter.restoreFilter(); @@ -37,10 +56,6 @@ public void onViewCreated(View view, Bundle savedInstanceState) { listView.setAdapter(adapter); placeholder = view.findViewById(R.id.no_notification_placeholder); - adapter.getFilter().filter("", new Filter.FilterListener() { - public void onFilterComplete(int count) { - placeholder.setVisibility(count == 0 ? View.VISIBLE : View.INVISIBLE); - } - }); + adapter.getFilter().filter("", count -> placeholder.setVisibility(count == 0 ? View.VISIBLE : View.INVISIBLE)); } } diff --git a/app/src/main/java/org/asteroidos/sync/fragments/DeviceDetailFragment.java b/app/src/main/java/org/asteroidos/sync/fragments/DeviceDetailFragment.java index 7894a7f9..981c6b5f 100644 --- a/app/src/main/java/org/asteroidos/sync/fragments/DeviceDetailFragment.java +++ b/app/src/main/java/org/asteroidos/sync/fragments/DeviceDetailFragment.java @@ -1,6 +1,6 @@ /* - * Copyright (C) 2016 - Florent Revest - * Doug Koellmer + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,6 +33,7 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.cardview.widget.CardView; import androidx.fragment.app.Fragment; @@ -56,10 +57,6 @@ public class DeviceDetailFragment extends Fragment { private SharedPreferences mSilenceModeSettings; private SharedPreferences mCallStateSettings; - private CheckBox mTimeSyncCheckBox; - private CheckBox mSilenceModeCheckBox; - private CheckBox mCallStateServiceCheckBox; - private FloatingActionButton mFab; private boolean mConnected = false; @@ -117,7 +114,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) { Intent iremove = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); iremove.putExtra("event", "removed"); iremove.putExtra("id", 0xa57e401d); - getActivity().sendBroadcast(iremove); + requireActivity().sendBroadcast(iremove); Intent ipost = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); ipost.putExtra("event", "posted"); @@ -127,18 +124,18 @@ public void onViewCreated(View view, Bundle savedInstanceState) { ipost.putExtra("appIcon", "ios-watch-vibrating"); ipost.putExtra("summary", getString(R.string.watch_finder)); ipost.putExtra("body", getString(R.string.phone_is_searching)); - getActivity().sendBroadcast(ipost); + requireActivity().sendBroadcast(ipost); }); CardView screenshotCard = view.findViewById(R.id.card_view3); - screenshotCard.setOnClickListener(view1 -> getActivity().sendBroadcast(new Intent("org.asteroidos.sync.SCREENSHOT_REQUEST_LISTENER"))); + screenshotCard.setOnClickListener(view1 -> requireActivity().sendBroadcast(new Intent("org.asteroidos.sync.SCREENSHOT_REQUEST_LISTENER"))); CardView notifSettCard = view.findViewById(R.id.card_view4); notifSettCard.setOnClickListener(notifSettCardView -> mAppSettingsListener.onAppSettingsClicked()); - mTimeSyncSettings = getActivity().getSharedPreferences(TimeService.PREFS_NAME, 0); + mTimeSyncSettings = requireActivity().getSharedPreferences(TimeService.PREFS_NAME, 0); - mTimeSyncCheckBox = view.findViewById(R.id.timeSyncCheckBox); + CheckBox mTimeSyncCheckBox = view.findViewById(R.id.timeSyncCheckBox); mTimeSyncCheckBox.setChecked(mTimeSyncSettings.getBoolean(TimeService.PREFS_SYNC_TIME, TimeService.PREFS_SYNC_TIME_DEFAULT)); mTimeSyncCheckBox.setOnCheckedChangeListener((ignored, checked) -> { SharedPreferences.Editor editor = mTimeSyncSettings.edit(); @@ -146,8 +143,8 @@ public void onViewCreated(View view, Bundle savedInstanceState) { editor.apply(); }); - mSilenceModeSettings = getActivity().getSharedPreferences(SilentModeService.PREFS_NAME, Context.MODE_PRIVATE); - mSilenceModeCheckBox = view.findViewById(R.id.SilentModeCheckBox); + mSilenceModeSettings = requireActivity().getSharedPreferences(SilentModeService.PREFS_NAME, Context.MODE_PRIVATE); + CheckBox mSilenceModeCheckBox = view.findViewById(R.id.SilentModeCheckBox); mSilenceModeCheckBox.setChecked(mSilenceModeSettings.getBoolean(SilentModeService.PREF_RINGER, false)); mSilenceModeCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { SharedPreferences.Editor editor = mSilenceModeSettings.edit(); @@ -155,8 +152,8 @@ public void onViewCreated(View view, Bundle savedInstanceState) { editor.apply(); }); - mCallStateSettings = getActivity().getSharedPreferences(PhoneStateReceiver.PREFS_NAME, Context.MODE_PRIVATE); - mCallStateServiceCheckBox = view.findViewById(R.id.CallStateServiceCheckBox); + mCallStateSettings = requireActivity().getSharedPreferences(PhoneStateReceiver.PREFS_NAME, Context.MODE_PRIVATE); + CheckBox mCallStateServiceCheckBox = view.findViewById(R.id.CallStateServiceCheckBox); mCallStateServiceCheckBox.setChecked(mCallStateSettings.getBoolean(PhoneStateReceiver.PREF_SEND_CALL_STATE, true)); mCallStateServiceCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { SharedPreferences.Editor editor = mCallStateSettings.edit(); @@ -168,7 +165,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) { } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.device_detail_manu, menu); super.onCreateOptionsMenu(menu, inflater); } @@ -182,7 +179,7 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { } public void setLocalName(String name) { - getActivity().setTitle(name); + requireActivity().setTitle(name); } public void setStatus(IAsteroidDevice.ConnectionState status) { @@ -230,36 +227,36 @@ public void scanningStopped() { } @Override - public void onAttach(Context context) { + public void onAttach(@NonNull Context context) { super.onAttach(context); if (context instanceof DeviceDetailFragment.OnDefaultDeviceUnselectedListener) mDeviceListener = (DeviceDetailFragment.OnDefaultDeviceUnselectedListener) context; else - throw new ClassCastException(context.toString() + throw new ClassCastException(context + " does not implement DeviceDetailFragment.OnDefaultDeviceUnselectedListener"); if (context instanceof DeviceDetailFragment.OnConnectRequestedListener) mConnectListener = (DeviceDetailFragment.OnConnectRequestedListener) context; else - throw new ClassCastException(context.toString() + throw new ClassCastException(context + " does not implement DeviceDetailFragment.OnConnectRequestedListener"); if (context instanceof DeviceDetailFragment.OnAppSettingsClickedListener) mAppSettingsListener = (DeviceDetailFragment.OnAppSettingsClickedListener) context; else - throw new ClassCastException(context.toString() + throw new ClassCastException(context + " does not implement DeviceDetailFragment.OnAppSettingsClickedListener"); if (context instanceof DeviceDetailFragment.OnWeatherSettingsClickedListener) mWeatherSettingsListener = (DeviceDetailFragment.OnWeatherSettingsClickedListener) context; else - throw new ClassCastException(context.toString() + throw new ClassCastException(context + " does not implement DeviceDetailFragment.OnWeatherSettingsClickedListener"); if (context instanceof DeviceDetailFragment.OnUpdateListener) mUpdateListener = (DeviceDetailFragment.OnUpdateListener) context; else - throw new ClassCastException(context.toString() + throw new ClassCastException(context + " does not implement DeviceDetailFragment.onUpdateListener"); } diff --git a/app/src/main/java/org/asteroidos/sync/fragments/DeviceListFragment.java b/app/src/main/java/org/asteroidos/sync/fragments/DeviceListFragment.java index 8d03ae3b..5e6755ea 100644 --- a/app/src/main/java/org/asteroidos/sync/fragments/DeviceListFragment.java +++ b/app/src/main/java/org/asteroidos/sync/fragments/DeviceListFragment.java @@ -1,6 +1,6 @@ /* - * Copyright (C) 2016 - Florent Revest - * Doug Koellmer + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +29,7 @@ import android.widget.ListView; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; @@ -109,18 +110,18 @@ public void deviceUndiscovered(BluetoothDevice dev) { } @Override - public void onAttach(Context context) { + public void onAttach(@NonNull Context context) { super.onAttach(context); if (context instanceof OnDefaultDeviceSelectedListener) mDeviceListener = (OnDefaultDeviceSelectedListener) context; else - throw new ClassCastException(context.toString() + throw new ClassCastException(context + " does not implement DeviceListFragment.OnDeviceSelectedListener"); if (context instanceof OnScanRequestedListener) mScanListener = (OnScanRequestedListener) context; else - throw new ClassCastException(context.toString() + throw new ClassCastException(context + " does not implement DeviceListFragment.OnScanRequestedListener"); } @@ -144,7 +145,7 @@ private class LeDeviceListAdapter extends BaseAdapter { LeDeviceListAdapter() { super(); - mLeDevices = new ArrayList(); + mLeDevices = new ArrayList<>(); mInflator = requireActivity().getLayoutInflater(); } diff --git a/app/src/main/java/org/asteroidos/sync/fragments/WeatherSettingsFragment.java b/app/src/main/java/org/asteroidos/sync/fragments/WeatherSettingsFragment.java index fb0fc387..61c42cd9 100644 --- a/app/src/main/java/org/asteroidos/sync/fragments/WeatherSettingsFragment.java +++ b/app/src/main/java/org/asteroidos/sync/fragments/WeatherSettingsFragment.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.fragments; import android.Manifest; @@ -5,12 +23,16 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; + import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; + import android.os.Bundle; + import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; + import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -19,7 +41,6 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; -import android.widget.CompoundButton; import android.widget.EditText; import org.asteroidos.sync.R; @@ -27,6 +48,7 @@ import org.osmdroid.api.IGeoPoint; import org.osmdroid.tileprovider.tilesource.TileSourceFactory; import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.CustomZoomButtonsController; import org.osmdroid.views.MapView; import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider; import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay; @@ -46,7 +68,7 @@ public class WeatherSettingsFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedInstanceState) { - mSettings = getContext().getSharedPreferences(WeatherService.PREFS_NAME, 0); + mSettings = requireActivity().getSharedPreferences(WeatherService.PREFS_NAME, 0); setHasOptionsMenu(true); return inflater.inflate(R.layout.fragment_position_picker, parent, false); @@ -62,64 +84,58 @@ public void onViewCreated(View view, Bundle savedInstanceState) { mMapView = view.findViewById(R.id.map); mMapView.setTileSource(TileSourceFactory.MAPNIK); mMapView.setMultiTouchControls(true); - mMapView.setBuiltInZoomControls(false); + mMapView.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER); mMapView.setZoomRounding(true); mMapView.setMaxZoomLevel(13.0); mMapView.setMinZoomLevel(5.0); mMapView.getController().setZoom(zoom); mMapView.getController().setCenter(new GeoPoint(latitude, longitude)); - if (ContextCompat.checkSelfPermission(getActivity(), + if (ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(getActivity(), + ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, WEATHER_LOCATION_PERMISSION_REQUEST); } else { - MyLocationNewOverlay mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(getContext()),mMapView); + MyLocationNewOverlay mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(requireContext()), mMapView); mLocationOverlay.enableMyLocation(); mMapView.getOverlays().add(mLocationOverlay); } mButton = view.findViewById(R.id.positionPickerButton); - mButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - IGeoPoint center = mMapView.getMapCenter(); - - float latitude = (float) center.getLatitude(); - float longitude = (float) center.getLongitude(); - float zoom = (float) mMapView.getZoomLevelDouble(); - - SharedPreferences.Editor editor = mSettings.edit(); - editor.putFloat(WeatherService.PREFS_LATITUDE, latitude); - editor.putFloat(WeatherService.PREFS_LONGITUDE, longitude); - editor.putFloat(WeatherService.PREFS_ZOOM, zoom); - editor.apply(); + mButton.setOnClickListener(v -> { + IGeoPoint center = mMapView.getMapCenter(); - // Update the Weather after changing it - getActivity().sendBroadcast(new Intent(WeatherService.WEATHER_SYNC_INTENT)); + float latitude1 = (float) center.getLatitude(); + float longitude1 = (float) center.getLongitude(); + float zoom1 = (float) mMapView.getZoomLevelDouble(); - getActivity().onBackPressed(); - } + SharedPreferences.Editor editor = mSettings.edit(); + editor.putFloat(WeatherService.PREFS_LATITUDE, latitude1); + editor.putFloat(WeatherService.PREFS_LONGITUDE, longitude1); + editor.putFloat(WeatherService.PREFS_ZOOM, zoom1); + editor.apply(); + + // Update the Weather after changing it + getActivity().sendBroadcast(new Intent(WeatherService.WEATHER_SYNC_INTENT)); + + getActivity().onBackPressed(); }); mWeatherSyncSettings = getActivity().getSharedPreferences(WeatherService.PREFS_NAME, 0); mWeatherSyncCheckBox = view.findViewById(R.id.autoLocationPickerButton); mWeatherSyncCheckBox.setChecked(mWeatherSyncSettings.getBoolean(WeatherService.PREFS_SYNC_WEATHER, WeatherService.PREFS_SYNC_WEATHER_DEFAULT)); - mWeatherSyncCheckBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton ignored, boolean checked) { - if (ContextCompat.checkSelfPermission(getActivity(), - Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(getActivity(), - new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, - WEATHER_LOCATION_SYNC_PERMISSION_REQUEST); - } else { - handleLocationToggle(mWeatherSyncCheckBox.isChecked()); - } + mWeatherSyncCheckBox.setOnCheckedChangeListener((ignored, checked) -> { + if (ContextCompat.checkSelfPermission(getActivity(), + Manifest.permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(getActivity(), + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + WEATHER_LOCATION_SYNC_PERMISSION_REQUEST); + } else { + handleLocationToggle(mWeatherSyncCheckBox.isChecked()); } }); @@ -127,7 +143,7 @@ public void onCheckedChanged(CompoundButton ignored, boolean checked) { } @Override - public void onResume(){ + public void onResume() { super.onResume(); mMapView.onResume(); } @@ -139,14 +155,14 @@ public void onPause() { } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.owm_position_picker_menu, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem menuItem) { - if (menuItem.getItemId() == R.id.customApiKey){ + if (menuItem.getItemId() == R.id.customApiKey) { View view = View.inflate(getActivity(), R.layout.dialog_api_key, null); EditText editText = view.findViewById(R.id.apikey); editText.setText(mOwmKey); @@ -177,12 +193,12 @@ private void handleLocationToggle(boolean enable) { editor.putBoolean(WeatherService.PREFS_SYNC_WEATHER, enable); editor.apply(); mButton.setVisibility(enable ? View.INVISIBLE : View.VISIBLE); - getActivity().sendBroadcast(new Intent(WeatherService.WEATHER_SYNC_INTENT)); + requireActivity().sendBroadcast(new Intent(WeatherService.WEATHER_SYNC_INTENT)); } @Override public void onRequestPermissionsResult(int requestCode, - @NonNull String permissions[], @NonNull int[] grantResults) { + @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case WEATHER_LOCATION_SYNC_PERMISSION_REQUEST: { // If request is cancelled, the result arrays are empty. @@ -198,7 +214,7 @@ public void onRequestPermissionsResult(int requestCode, case WEATHER_LOCATION_PERMISSION_REQUEST: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - MyLocationNewOverlay mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(getContext()), mMapView); + MyLocationNewOverlay mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(requireContext()), mMapView); mLocationOverlay.enableMyLocation(); mMapView.getOverlays().add(mLocationOverlay); } diff --git a/app/src/main/java/org/asteroidos/sync/services/AutostartService.java b/app/src/main/java/org/asteroidos/sync/services/AutostartService.java index 51696df4..1f2aafbf 100644 --- a/app/src/main/java/org/asteroidos/sync/services/AutostartService.java +++ b/app/src/main/java/org/asteroidos/sync/services/AutostartService.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.services; import android.content.BroadcastReceiver; diff --git a/app/src/main/java/org/asteroidos/sync/services/GPSTracker.java b/app/src/main/java/org/asteroidos/sync/services/GPSTracker.java index b704c7d9..87c94ba5 100644 --- a/app/src/main/java/org/asteroidos/sync/services/GPSTracker.java +++ b/app/src/main/java/org/asteroidos/sync/services/GPSTracker.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.services; import android.app.Service; diff --git a/app/src/main/java/org/asteroidos/sync/services/GenericFileProvider.java b/app/src/main/java/org/asteroidos/sync/services/GenericFileProvider.java index 6296f2ee..0a04db07 100644 --- a/app/src/main/java/org/asteroidos/sync/services/GenericFileProvider.java +++ b/app/src/main/java/org/asteroidos/sync/services/GenericFileProvider.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.services; import androidx.core.content.FileProvider; diff --git a/app/src/main/java/org/asteroidos/sync/services/NLService.java b/app/src/main/java/org/asteroidos/sync/services/NLService.java index 73bb5c9e..d8626c73 100644 --- a/app/src/main/java/org/asteroidos/sync/services/NLService.java +++ b/app/src/main/java/org/asteroidos/sync/services/NLService.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2016 - Florent Revest + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +18,6 @@ package org.asteroidos.sync.services; -import android.annotation.TargetApi; import android.app.Notification; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -38,6 +38,7 @@ import java.util.Arrays; import java.util.Hashtable; import java.util.Map; +import java.util.concurrent.TimeUnit; public class NLService extends NotificationListenerService { private NLServiceReceiver nlServiceReceiver; @@ -160,11 +161,11 @@ public void onNotificationPosted(StatusBarNotification sbn) { String packageName = sbn.getPackageName(); String[] allowedOngoingApps = {"com.google.android.apps.maps", "org.thoughtcrime.securesms"}; - if((notification.priority < Notification.PRIORITY_DEFAULT) || - ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0 - && !Arrays.asList(allowedOngoingApps).contains(packageName)) || - (NotificationCompat.getLocalOnly(notification)) || - (NotificationCompat.isGroupSummary(notification))) + if ((notification.priority < Notification.PRIORITY_DEFAULT) || + ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0 + && !Arrays.asList(allowedOngoingApps).contains(packageName)) || + (NotificationCompat.getLocalOnly(notification)) || + (NotificationCompat.isGroupSummary(notification))) return; NotificationParser notifParser = new NotificationParser(notification); @@ -178,16 +179,17 @@ public void onNotificationPosted(StatusBarNotification sbn) { final PackageManager pm = getApplicationContext().getPackageManager(); ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); appName = pm.getApplicationLabel(ai).toString(); - } catch (PackageManager.NameNotFoundException ignored) {} + } catch (PackageManager.NameNotFoundException ignored) { + } - if(summary == null) summary = ""; - else summary = summary.trim(); - if(body == null) body = ""; - else body = body.trim(); - if(packageName == null) packageName = ""; - if(appIcon == null) appIcon = ""; + if (summary == null) summary = ""; + else summary = summary.trim(); + if (body == null) body = ""; + else body = body.trim(); + if (packageName == null) packageName = ""; + if (appIcon == null) appIcon = ""; - Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); + Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); i.putExtra("event", "posted"); i.putExtra("packageName", packageName); i.putExtra("id", id); @@ -208,7 +210,6 @@ public void onNotificationRemoved(StatusBarNotification sbn) { } @Override - @TargetApi(Build.VERSION_CODES.N) public void onListenerDisconnected() { listenerConnected = false; // Notification listener disconnected - requesting rebind @@ -227,9 +228,16 @@ public void onReceive(Context context, Intent intent) { if (intent.getStringExtra("command").equals("refresh")) { Handler handler = new Handler(); handler.postDelayed(() -> { - while (!listenerConnected); + while (!listenerConnected) { + // Sleep the spin + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Thread.onSpinWait(); + }else { + // Will not delay here, as we can cause the entire UI to freeze + } + } StatusBarNotification[] notifs = getActiveNotifications(); - for(StatusBarNotification notif : notifs) + for (StatusBarNotification notif : notifs) onNotificationPosted(notif); }, 500); } diff --git a/app/src/main/java/org/asteroidos/sync/services/PhoneStateReceiver.java b/app/src/main/java/org/asteroidos/sync/services/PhoneStateReceiver.java index 1edbee17..8daa3e6a 100644 --- a/app/src/main/java/org/asteroidos/sync/services/PhoneStateReceiver.java +++ b/app/src/main/java/org/asteroidos/sync/services/PhoneStateReceiver.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.services; import android.app.Activity; @@ -34,10 +52,11 @@ public void onReceive(Context context, Intent intent) { } static class CallStateService extends PhoneStateListener { - private Context context; - private SharedPreferences prefs; + private final Context context; + private final SharedPreferences prefs; CallStateService(Context con) { + super(); context = con; prefs = con.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); } diff --git a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java index 906caf67..6831fc71 100644 --- a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java +++ b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2016 - Florent Revest + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,11 +29,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; -import android.os.Handler; import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; import android.util.Log; import androidx.annotation.NonNull; @@ -51,13 +48,14 @@ import org.asteroidos.sync.connectivity.SilentModeService; import org.asteroidos.sync.connectivity.TimeService; import org.asteroidos.sync.connectivity.WeatherService; +import org.asteroidos.sync.viewmodel.base.SynchronizationServiceModel; +import org.asteroidos.sync.viewmodel.impl.SynchronizationServiceModelImpl; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; import no.nordicsemi.android.ble.observer.ConnectionObserver; @@ -74,7 +72,6 @@ public class SynchronizationService extends Service implements IAsteroidDevice, public static final int MSG_UNSET_DEVICE = 9; private static final String NOTIFICATION_CHANNEL_ID = "synchronizationservice_channel_id_01"; - final Messenger mMessenger = new Messenger(new SynchronizationHandler(this)); private final int NOTIFICATION = 2725; public BluetoothDevice mDevice; public int batteryPercentage = 0; @@ -82,16 +79,18 @@ public class SynchronizationService extends Service implements IAsteroidDevice, List nonBleServices; private NotificationManager mNM; private ConnectionState mState = ConnectionState.STATUS_DISCONNECTED; - private Messenger replyTo; private SharedPreferences mPrefs; private AsteroidBleManager mBleMngr; + private final SynchronizationServiceModel model = new SynchronizationServiceModelImpl(); + final void handleConnect() { if (mBleMngr == null) { mBleMngr = new AsteroidBleManager(getApplicationContext(), SynchronizationService.this); mBleMngr.setConnectionObserver(this); } - if (mState == ConnectionState.STATUS_CONNECTED || mState == ConnectionState.STATUS_CONNECTING) return; + if (mState == ConnectionState.STATUS_CONNECTED || mState == ConnectionState.STATUS_CONNECTING) + return; mPrefs = getSharedPreferences(MainActivity.PREFS_NAME, Context.MODE_PRIVATE); String defaultDevMacAddr = mPrefs.getString(MainActivity.PREFS_DEFAULT_MAC_ADDR, ""); @@ -106,9 +105,9 @@ final void handleConnect() { .retry(3, 200) .done(device1 -> { Log.d(TAG, "Connected to " + device1.getName()); - // Now we read the current values of the GATT characteristics, - // _after_ the connection has been fully established, to avoid - // connection failures on Android 12 and later. + // Now we read the current values of the GATT characteristics, + // _after_ the connection has been fully established, to avoid + // connection failures on Android 12 and later. mBleMngr.readCharacteristics(); }) .fail((device2, error) -> Log.e(TAG, "Failed to connect to " + device.getName() + @@ -135,11 +134,8 @@ final void handleSetDevice(BluetoothDevice device) { mDevice = device; try { String name = mDevice.getName(); - Message answer = Message.obtain(null, MSG_SET_LOCAL_NAME); - answer.obj = name; - replyTo.send(answer); - replyTo.send(Message.obtain(null, MSG_SET_STATUS, mState)); - } catch (RemoteException | SecurityException | NullPointerException ignored) { + model.setLocalName(name); + } catch (SecurityException | NullPointerException ignored) { } editor.putString(MainActivity.PREFS_DEFAULT_LOC_NAME, name); editor.apply(); @@ -147,10 +143,7 @@ final void handleSetDevice(BluetoothDevice device) { final void handleUpdateConnectionStatus() { if (mDevice != null) { - try { - replyTo.send(Message.obtain(null, MSG_SET_STATUS, mState)); - } catch (RemoteException | NullPointerException ignored) { - } + model.setConnectionState(mState); } } @@ -288,6 +281,13 @@ public void onCreate() { handleConnect(); updateNotification(); + + model.onConnectRequested(this::handleConnect); + model.onDisconnectRequested(this::handleDisconnect); + model.onBatteryLifeRequested(this::handleUpdateBatteryPercentageRequest); + model.onDeviceSetRequested(this::handleSetDevice); + model.onDeviceUnsetRequested(this::handleUnSetDevice); + model.onDeviceUpdateRequested(this::handleUpdateConnectionStatus); } @Override @@ -337,7 +337,7 @@ public void onDestroy() { @Override public IBinder onBind(Intent intent) { - return mMessenger.getBinder(); + return null; } @Override @@ -358,52 +358,15 @@ private void handleUnSetDevice() { editor.apply(); } + public void handleUpdateBatteryPercentageRequest() { + AsteroidBleManager.BatteryLevelEvent batteryLevelEvent = new AsteroidBleManager.BatteryLevelEvent(); + batteryLevelEvent.battery = batteryPercentage; + handleUpdateBatteryPercentage(batteryLevelEvent); + } + public void handleUpdateBatteryPercentage(AsteroidBleManager.BatteryLevelEvent battery) { Log.d(TAG, "handleBattery: " + battery.battery + "%"); batteryPercentage = battery.battery; - try { - if (replyTo != null) - replyTo.send(Message.obtain(null, MSG_SET_BATTERY_PERCENTAGE, batteryPercentage, 0)); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - static private class SynchronizationHandler extends Handler { - private final SynchronizationService mService; - - SynchronizationHandler(SynchronizationService service) { - mService = service; - } - - @Override - public void handleMessage(Message msg) { - mService.replyTo = msg.replyTo; - - switch (msg.what) { - case MSG_CONNECT: - mService.handleConnect(); - break; - case MSG_DISCONNECT: - mService.handleDisconnect(); - break; - case MSG_REQUEST_BATTERY_LIFE: - AsteroidBleManager.BatteryLevelEvent batteryLevelEvent = new AsteroidBleManager.BatteryLevelEvent(); - batteryLevelEvent.battery = mService.batteryPercentage; - mService.handleUpdateBatteryPercentage(batteryLevelEvent); - break; - case MSG_SET_DEVICE: - mService.handleSetDevice((BluetoothDevice) msg.obj); - break; - case MSG_UNSET_DEVICE: - mService.handleUnSetDevice(); - break; - case MSG_UPDATE: - mService.handleUpdateConnectionStatus(); - break; - default: - super.handleMessage(msg); - } - } + model.setBatteryPercentage(batteryPercentage); } } diff --git a/app/src/main/java/org/asteroidos/sync/utils/AppInfo.java b/app/src/main/java/org/asteroidos/sync/utils/AppInfo.java index eade707e..142889c2 100644 --- a/app/src/main/java/org/asteroidos/sync/utils/AppInfo.java +++ b/app/src/main/java/org/asteroidos/sync/utils/AppInfo.java @@ -1,76 +1,80 @@ package org.asteroidos.sync.utils; +// TODO Handle dubious copyright // copied from https://github.com/jensstein/oandbackup, used under MIT license import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; + import androidx.annotation.NonNull; -public class AppInfo - implements Comparable, Parcelable -{ - private String label, packageName; - private boolean system, installed, checked, disabled; +public class AppInfo implements Comparable, Parcelable { + + private final String label, packageName; + private final boolean system, installed; + private boolean checked, disabled; + public Bitmap icon; - AppInfo(String packageName, String label, boolean system, boolean installed) - { + AppInfo(String packageName, String label, boolean system, boolean installed) { this.label = label; this.packageName = packageName; this.system = system; this.installed = installed; } - public String getPackageName() - { + + public String getPackageName() { return packageName; } - public String getLabel() - { + + public String getLabel() { return label; } - public void setDisabled(boolean disabled) - { + public void setDisabled(boolean disabled) { this.disabled = disabled; } - public boolean isDisabled() - { + + public boolean isDisabled() { return disabled; } - public boolean isSystem() - { + + public boolean isSystem() { return system; } - public int compareTo(@NonNull AppInfo appInfo) { return label.compareToIgnoreCase(appInfo.getLabel()); } - public String toString() - { + + public int compareTo(@NonNull AppInfo appInfo) { + return label.compareToIgnoreCase(appInfo.getLabel()); + } + + @NonNull + public String toString() { return label + " : " + packageName; } - public int describeContents() - { + + public int describeContents() { return 0; } - public void writeToParcel(Parcel out, int flags) - { + + public void writeToParcel(Parcel out, int flags) { out.writeString(label); out.writeString(packageName); - out.writeBooleanArray(new boolean[] {system, installed, checked}); + out.writeBooleanArray(new boolean[]{system, installed, checked}); out.writeParcelable(icon, flags); } - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() - { - public AppInfo createFromParcel(Parcel in) - { - return new AppInfo (in); + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() { + public AppInfo createFromParcel(Parcel in) { + return new AppInfo(in); } - public AppInfo[] newArray(int size) - { + + public AppInfo[] newArray(int size) { return new AppInfo[size]; } }; - private AppInfo(Parcel in) - { + + private AppInfo(Parcel in) { label = in.readString(); packageName = in.readString(); boolean[] bools = new boolean[4]; diff --git a/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java b/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java index 4ce4f3a2..e3296083 100644 --- a/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java +++ b/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java @@ -1,5 +1,24 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.utils; +// TODO Handle dubious copyright // copied from https://github.com/jensstein/oandbackup, used under MIT license import android.content.Context; @@ -13,7 +32,6 @@ import android.util.Log; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -26,16 +44,12 @@ public static ArrayList getPackageInfo(Context context) ArrayList list = new ArrayList<>(); PackageManager pm = context.getPackageManager(); List pinfoList = pm.getInstalledPackages(0); - Collections.sort(pinfoList, pInfoPackageNameComparator); + pinfoList.sort(pInfoPackageNameComparator); // list seemingly starts scrambled on 4.3 for(PackageInfo pinfo : pinfoList) { - boolean isSystem = false; - if((pinfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) - { - isSystem = true; - } + boolean isSystem = (pinfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; Bitmap icon = null; Drawable apkIcon = pm.getApplicationIcon(pinfo.applicationInfo); try @@ -68,11 +82,5 @@ public static ArrayList getPackageInfo(Context context) } return list; } - private static Comparator pInfoPackageNameComparator = new Comparator() - { - public int compare(PackageInfo p1, PackageInfo p2) - { - return p1.packageName.compareToIgnoreCase(p2.packageName); - } - }; + private static final Comparator pInfoPackageNameComparator = (p1, p2) -> p1.packageName.compareToIgnoreCase(p2.packageName); } diff --git a/app/src/main/java/org/asteroidos/sync/utils/AsteroidUUIDS.java b/app/src/main/java/org/asteroidos/sync/utils/AsteroidUUIDS.java index 14f9c9ec..7bbdf43d 100644 --- a/app/src/main/java/org/asteroidos/sync/utils/AsteroidUUIDS.java +++ b/app/src/main/java/org/asteroidos/sync/utils/AsteroidUUIDS.java @@ -1,3 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + /* AsteroidOS UUID collection for ble characteristics and watch filtering */ package org.asteroidos.sync.utils; diff --git a/app/src/main/java/org/asteroidos/sync/utils/NotificationParser.java b/app/src/main/java/org/asteroidos/sync/utils/NotificationParser.java index 99e55e16..14f1b668 100644 --- a/app/src/main/java/org/asteroidos/sync/utils/NotificationParser.java +++ b/app/src/main/java/org/asteroidos/sync/utils/NotificationParser.java @@ -1,7 +1,24 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.asteroidos.sync.utils; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.Notification; import android.graphics.Typeface; import android.os.Build; @@ -14,8 +31,6 @@ import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.List; // Originally from https://github.com/matejdro/PebbleNotificationCenter-Android written by Matej Drobnič under the terms of the GPLv3 @@ -35,7 +50,6 @@ public NotificationParser(Notification notification) getExtraBigData(notification); } - @TargetApi(value = Build.VERSION_CODES.JELLY_BEAN) private boolean tryParseNatively(Notification notification) { Bundle extras = notification.extras; @@ -101,12 +115,7 @@ private boolean parseMessageStyleNotification(Notification notification, Bundle summary = ""; List messagesDescending = new ArrayList<>(messagingStyle.getMessages()); - Collections.sort(messagesDescending, new Comparator() { - @Override - public int compare(NotificationCompat.MessagingStyle.Message m1, NotificationCompat.MessagingStyle.Message m2) { - return (int) (m2.getTimestamp() - m1.getTimestamp()); - } - }); + messagesDescending.sort((m1, m2) -> (int) (m2.getTimestamp() - m1.getTimestamp())); StringBuilder sb = new StringBuilder(); body = ""; @@ -114,10 +123,10 @@ public int compare(NotificationCompat.MessagingStyle.Message m1, NotificationCom for (NotificationCompat.MessagingStyle.Message message : messagesDescending) { String sender; - if (message.getSender() == null) - sender = formatCharSequence(messagingStyle.getUserDisplayName()); + if (message.getPerson() == null) + sender = formatCharSequence(messagingStyle.getUser().getName()); else - sender = formatCharSequence(message.getSender()); + sender = formatCharSequence(message.getPerson().getName()); sb.append(sender); sb.append(": "); @@ -130,7 +139,6 @@ public int compare(NotificationCompat.MessagingStyle.Message m1, NotificationCom return true; } - @TargetApi(value = Build.VERSION_CODES.JELLY_BEAN) private boolean parseInboxNotification(Bundle extras) { CharSequence summaryTextSequence = extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT); @@ -214,7 +222,6 @@ private void getExtraData(Notification notification) { parseRemoteView(views); } - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void getExtraBigData(Notification notification) { RemoteViews views; try { diff --git a/app/src/main/java/org/asteroidos/sync/viewmodel/base/MainActivityViewModel.kt b/app/src/main/java/org/asteroidos/sync/viewmodel/base/MainActivityViewModel.kt new file mode 100644 index 00000000..32eaf44e --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/viewmodel/base/MainActivityViewModel.kt @@ -0,0 +1,75 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.viewmodel.base + +import android.bluetooth.BluetoothDevice +import androidx.lifecycle.ViewModel +import org.asteroidos.sync.asteroid.IAsteroidDevice +import java.util.function.Consumer + +/** + * View model for the main activity + */ +abstract class MainActivityViewModel : ViewModel() { + + /** + * Listen to changes to local name + */ + abstract fun onWatchLocalNameChanged(consumer: Consumer) + + /** + * Listen to connection state changes + */ + abstract fun onWatchConnectionStateChanged(consumer: Consumer) + + /** + * Listen to battery percentage changes + */ + abstract fun onWatchBatteryPercentageChanged(consumer: Consumer) + + /** + * Request the device to be disconnected + */ + abstract fun requestDisconnect() + + /** + * Request to connect to the device + */ + abstract fun requestConnect() + + /** + * Request an update from the device + */ + abstract fun requestUpdate() + + /** + * Request the device to be unset + */ + abstract fun requestUnsetDevice() + + /** + * Select device + */ + abstract fun onDefaultDeviceSelected(mDevice: BluetoothDevice) + + /** + * Request battery level of the device + */ + abstract fun requestBatteryLevel() +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/viewmodel/base/SynchronizationServiceModel.kt b/app/src/main/java/org/asteroidos/sync/viewmodel/base/SynchronizationServiceModel.kt new file mode 100644 index 00000000..e699495e --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/viewmodel/base/SynchronizationServiceModel.kt @@ -0,0 +1,77 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.viewmodel.base + +import android.bluetooth.BluetoothDevice +import androidx.core.util.Consumer +import kotlinx.coroutines.Runnable +import org.asteroidos.sync.asteroid.IAsteroidDevice +import org.asteroidos.sync.asteroid.IAsteroidDevice.ConnectionState + +/** + * Model for the synchronization service to bind to kotlin flows. + * In the future this can be removed. + */ +interface SynchronizationServiceModel { + + /** + * Listen to connection requests + */ + fun onConnectRequested(consumer: Runnable) + + /** + * Listen to disconnect requests + */ + fun onDisconnectRequested(consumer: Runnable) + + /** + * Listen to battery life requests + */ + fun onBatteryLifeRequested(consumer: Runnable) + + /** + * Listen to device set requests + */ + fun onDeviceSetRequested(consumer: Consumer) + + /** + * Listen to device unset requests + */ + fun onDeviceUnsetRequested(consumer: Runnable) + + /** + * Listen to device update requests + */ + fun onDeviceUpdateRequested(consumer: Runnable) + + /** + * Set the local name + */ + fun setLocalName(name: String) + + /** + * Set the device connection state + */ + fun setConnectionState(state: ConnectionState) + + /** + * Set the battery percentage + */ + fun setBatteryPercentage(percentage: Int) +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/viewmodel/impl/MainActivityViewModelImpl.kt b/app/src/main/java/org/asteroidos/sync/viewmodel/impl/MainActivityViewModelImpl.kt new file mode 100644 index 00000000..0abbeb17 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/viewmodel/impl/MainActivityViewModelImpl.kt @@ -0,0 +1,104 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.viewmodel.impl + +import android.bluetooth.BluetoothDevice +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.asteroidos.sync.asteroid.IAsteroidDevice +import org.asteroidos.sync.common.ext.logV +import org.asteroidos.sync.domain.repository.base.MessageRepository +import org.asteroidos.sync.domain.repository.base.WatchStatusRepository +import org.asteroidos.sync.viewmodel.base.MainActivityViewModel +import java.util.function.Consumer + +/** + * Implementation of the main activity view model + * + * TODO use android DI framework + */ +class MainActivityViewModelImpl( + private val messageRepo: MessageRepository = MessageRepository.get(), + private val watchStatusRepo: WatchStatusRepository = WatchStatusRepository.get() +) : MainActivityViewModel() { + + override fun onWatchLocalNameChanged(consumer: Consumer) { + viewModelScope.launch(Dispatchers.IO) { + watchStatusRepo.deviceName.collect { + logV("onWatchLocalNameChanged") + viewModelScope.launch { + consumer.accept(it) + } + } + } + } + + override fun onWatchConnectionStateChanged(consumer: Consumer) { + viewModelScope.launch(Dispatchers.IO) { + watchStatusRepo.connectionState.collect { + logV("onWatchConnectionStateChanged") + viewModelScope.launch { + consumer.accept(it) + } + } + } + } + + override fun onWatchBatteryPercentageChanged(consumer: Consumer) { + viewModelScope.launch(Dispatchers.IO) { + watchStatusRepo.batteryPercentage.collect { + logV("onWatchBatteryPercentageChanged") + viewModelScope.launch { + consumer.accept(it) + } + } + } + } + + override fun requestDisconnect() { + logV("") + messageRepo.requestDisconnect() + } + + override fun requestConnect() { + logV("") + messageRepo.requestConnect() + } + + override fun requestUpdate() { + logV("") + messageRepo.requestUpdateDevice() + } + + override fun requestUnsetDevice() { + logV("") + messageRepo.requestUnsetDevice() + } + + override fun onDefaultDeviceSelected(mDevice: BluetoothDevice) { + logV("") + messageRepo.requestSetDevice(mDevice) + } + + override fun requestBatteryLevel() { + logV("") + messageRepo.requestBatteryLife() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/viewmodel/impl/SynchronizationServiceModelImpl.kt b/app/src/main/java/org/asteroidos/sync/viewmodel/impl/SynchronizationServiceModelImpl.kt new file mode 100644 index 00000000..1bb6d1d8 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/viewmodel/impl/SynchronizationServiceModelImpl.kt @@ -0,0 +1,97 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.viewmodel.impl + +import android.bluetooth.BluetoothDevice +import androidx.core.util.Consumer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Runnable +import kotlinx.coroutines.launch +import org.asteroidos.sync.asteroid.IAsteroidDevice.ConnectionState +import org.asteroidos.sync.domain.repository.base.MessageRepository +import org.asteroidos.sync.domain.repository.base.WatchStatusRepository +import org.asteroidos.sync.viewmodel.base.SynchronizationServiceModel + +class SynchronizationServiceModelImpl( + private val messageRepo: MessageRepository = MessageRepository.get(), + private val watchStatusRepo: WatchStatusRepository = WatchStatusRepository.get() +) : SynchronizationServiceModel { + private val modelScope = CoroutineScope(Dispatchers.IO) + + override fun onConnectRequested(consumer: Runnable) { + modelScope.launch { + messageRepo.requestConnectFlow.collect { + consumer.run() + } + } + } + + override fun onDisconnectRequested(consumer: Runnable) { + modelScope.launch { + messageRepo.requestDisconnectFlow.collect { + consumer.run() + } + } + } + + override fun onBatteryLifeRequested(consumer: Runnable) { + modelScope.launch { + messageRepo.requestBatteryLifeFlow.collect { + consumer.run() + } + } + } + + override fun onDeviceSetRequested(consumer: Consumer) { + modelScope.launch { + messageRepo.requestSetDeviceFlow.collect { + consumer.accept(it) + } + } + } + + override fun onDeviceUnsetRequested(consumer: Runnable) { + modelScope.launch { + messageRepo.requestUnsetDeviceFlow.collect { + consumer.run() + } + } + } + + override fun onDeviceUpdateRequested(consumer: Runnable) { + modelScope.launch { + messageRepo.requestUpdateDeviceFlow.collect { + consumer.run() + } + } + } + + override fun setLocalName(name: String) { + watchStatusRepo.setDeviceName(name) + } + + override fun setConnectionState(state: ConnectionState) { + watchStatusRepo.setConnectionState(state) + } + + override fun setBatteryPercentage(percentage: Int) { + watchStatusRepo.setBatteryPercentage(percentage) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index e1a873fb..5f423d05 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,8 @@ buildscript { google() } dependencies { - classpath("com.android.tools.build:gradle:7.3.0") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21") + classpath("com.android.tools.build:gradle:7.4.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10") } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 73cd3fa1..d6c35de1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip