diff --git a/Android.bp b/Android.bp index 7b072fc878d79c7289b417f8518807956ea9c1ca..55ec76cf973c1d74aa1254a8cfcf6422a558c438 100644 --- a/Android.bp +++ b/Android.bp @@ -20,12 +20,16 @@ android_app { // Include SettingsLib and its dependencies defaults: ["SettingsLibDefaults"], - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], resource_dirs: ["res"], static_libs: [ "com.google.android.material_material", "androidx.core_core", + "androidx.core_core-ktx", "androidx.appcompat_appcompat", "androidx.cardview_cardview", "androidx.localbroadcastmanager_localbroadcastmanager", diff --git a/build.gradle b/build.gradle index 3a2eb827d13776c4d8cf4ce49a17a787ef91fcfd..7c5e5a031133192afddd4d4b7dc9b2296acc442a 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.1.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10" } } @@ -19,6 +20,7 @@ def keystoreProperties = new Properties() keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) apply plugin: 'com.android.application' +apply plugin: 'org.jetbrains.kotlin.android' android { compileSdkVersion 31 @@ -58,9 +60,10 @@ android { dependencies { compileOnly fileTree(dir: 'system_libs/', include: ['*.jar']) - implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'com.google.android.material:material:1.4.0' } diff --git a/res/layout/preferences_dialog.xml b/res/layout/preferences_dialog.xml index c586f44e049a71be2554abd00b925b2cff82fab4..2537dccbc208b98860d96dd7809b0f4eef1d9804 100644 --- a/res/layout/preferences_dialog.xml +++ b/res/layout/preferences_dialog.xml @@ -25,8 +25,7 @@ android:id="@+id/preferences_auto_updates_check_interval" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_weight="1" - android:entries="@array/menu_auto_updates_check_interval_entries" /> + android:layout_weight="1" /> @string/menu_auto_updates_check_interval_weekly @string/menu_auto_updates_check_interval_monthly + + + @string/menu_auto_updates_check_interval_5_minutes + @string/menu_auto_updates_check_interval_10_minutes + @string/menu_auto_updates_check_interval_30_minutes + diff --git a/res/values/strings.xml b/res/values/strings.xml index ac7427af89909d101a4be23d5e168a2a76a534ca..a11e7439557bf3081033632b98fe91a89b125ce0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -67,6 +67,9 @@ Refresh Preferences Auto updates check + Every 5 minutes + Every 10 minutes + Every 30 minutes Once a day Once a week Once a month diff --git a/src/org/lineageos/updater/UpdatesActivity.java b/src/org/lineageos/updater/UpdatesActivity.java index b27afc35571651c3e415d7ccc2407d02ee4949ce..408aff154ad5e980a48ad11c1a5d212e4b6c4a6e 100644 --- a/src/org/lineageos/updater/UpdatesActivity.java +++ b/src/org/lineageos/updater/UpdatesActivity.java @@ -41,6 +41,7 @@ import android.view.View; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; +import android.widget.ArrayAdapter; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -75,6 +76,7 @@ import org.lineageos.updater.model.UpdateInfo; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -491,7 +493,25 @@ public class UpdatesActivity extends UpdatesListActivity { } SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + + List intervalList = new ArrayList<>(Arrays.asList(getResources().getStringArray( + R.array.menu_auto_updates_check_interval_entries))); + + if (Utils.isDevModeOn(this)) { + // Add additional intervals while enabling developer options is on + intervalList.addAll(Arrays.asList(getResources().getStringArray( + R.array.menu_auto_updates_check_interval_entries_dev))); + } else if (Utils.getUpdateCheckSetting(this) > 3) { + prefs.edit().putInt(Constants.PREF_AUTO_UPDATES_CHECK_INTERVAL, + Constants.AUTO_UPDATES_CHECK_INTERVAL_DAILY).apply(); + } + + ArrayAdapter adapter = new ArrayAdapter<>(this, + android.R.layout.simple_list_item_1, intervalList); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + autoCheckInterval.setAdapter(adapter); autoCheckInterval.setSelection(Utils.getUpdateCheckSetting(this)); + autoDelete.setChecked(prefs.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, true)); dataWarning.setChecked(prefs.getBoolean(Constants.PREF_MOBILE_DATA_WARNING, true)); abPerfMode.setChecked(prefs.getBoolean(Constants.PREF_AB_PERF_MODE, false)); diff --git a/src/org/lineageos/updater/UpdatesCheckReceiver.java b/src/org/lineageos/updater/UpdatesCheckReceiver.java index 07beb1a7f6166dd81b5280756dd3d6e948994a6f..d16e9c5b0131fc3ea39d91318b04294d67823aec 100644 --- a/src/org/lineageos/updater/UpdatesCheckReceiver.java +++ b/src/org/lineageos/updater/UpdatesCheckReceiver.java @@ -23,6 +23,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.os.SystemClock; import android.util.Log; @@ -30,7 +33,9 @@ import androidx.core.app.NotificationCompat; import androidx.preference.PreferenceManager; import org.json.JSONException; +import org.lineageos.updater.controller.UpdaterService; import org.lineageos.updater.download.DownloadClient; +import org.lineageos.updater.misc.ConnectionStateMonitor; import org.lineageos.updater.misc.Constants; import org.lineageos.updater.misc.Utils; @@ -49,6 +54,15 @@ public class UpdatesCheckReceiver extends BroadcastReceiver { private static final String NEW_UPDATES_NOTIFICATION_CHANNEL = "new_updates_notification_channel"; + private final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .build(); + + private ConnectivityManager.NetworkCallback cnState; + private ConnectivityManager connectivityManager; + @Override public void onReceive(final Context context, Intent intent) { if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { @@ -62,15 +76,21 @@ public class UpdatesCheckReceiver extends BroadcastReceiver { return; } - if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - // Set a repeating alarm on boot to check for new updates once per day - scheduleRepeatingUpdatesCheck(context); - } + // Exact alarms does not support repeating, so update to make it + // work like repeating alarms. To check for update at the exact time. + updateRepeatingUpdatesCheck(context); + + cnState = new ConnectionStateMonitor().getInstance(context); + connectivityManager = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); if (!Utils.isNetworkAvailable(context)) { - Log.d(TAG, "Network not available, scheduling new check"); - scheduleUpdatesCheck(context); + if (!UpdaterService.isNetworkCallBackActive()) { + setupNetworkCallback(true); + } return; + } else if (UpdaterService.isNetworkCallBackActive()) { + setupNetworkCallback(false); } final File json = Utils.getCachedUpdateList(context); @@ -122,6 +142,21 @@ public class UpdatesCheckReceiver extends BroadcastReceiver { } } + public void setupNetworkCallback(boolean shouldEnable) { + if (shouldEnable) { + connectivityManager.registerNetworkCallback(networkRequest, cnState); + } else { + try { + connectivityManager.unregisterNetworkCallback(cnState); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Network callback was not registered"); + } + } + + UpdaterService.setNetworkCallBackActive(shouldEnable); + Log.d(TAG, "Network callback enabled: " + shouldEnable); + } + private static void showNotification(Context context) { NotificationManager notificationManager = context.getSystemService( NotificationManager.class); @@ -160,12 +195,10 @@ public class UpdatesCheckReceiver extends BroadcastReceiver { PendingIntent updateCheckIntent = getRepeatingUpdatesCheckIntent(context); AlarmManager alarmMgr = context.getSystemService(AlarmManager.class); - alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC, - System.currentTimeMillis() + Utils.getUpdateCheckInterval(context), - updateCheckIntent); + long nextCheck = System.currentTimeMillis() + Utils.getUpdateCheckInterval(context); + alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextCheck, updateCheckIntent); - Date nextCheckDate = new Date(System.currentTimeMillis() + - Utils.getUpdateCheckInterval(context)); + Date nextCheckDate = new Date(nextCheck); Log.d(TAG, "Setting automatic updates check: " + nextCheckDate); } @@ -181,14 +214,14 @@ public class UpdatesCheckReceiver extends BroadcastReceiver { } public static void scheduleUpdatesCheck(Context context) { - long millisToNextCheck = AlarmManager.INTERVAL_HOUR * 2; + long millisToNextCheck = AlarmManager.INTERVAL_FIFTEEN_MINUTES; PendingIntent updateCheckIntent = getUpdatesCheckIntent(context); AlarmManager alarmMgr = context.getSystemService(AlarmManager.class); + long nextCheck = SystemClock.elapsedRealtime() + millisToNextCheck; alarmMgr.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, - SystemClock.elapsedRealtime() + millisToNextCheck, - updateCheckIntent); + nextCheck, updateCheckIntent); - Date nextCheckDate = new Date(System.currentTimeMillis() + millisToNextCheck); + Date nextCheckDate = new Date(nextCheck); Log.d(TAG, "Setting one-shot updates check: " + nextCheckDate); } diff --git a/src/org/lineageos/updater/controller/UpdaterService.java b/src/org/lineageos/updater/controller/UpdaterService.java index c0354f92e2835d7f24c73ebe9f10d92c57170024..90544c4e2c887d2414736b2e53a0c5e7423d4d49 100644 --- a/src/org/lineageos/updater/controller/UpdaterService.java +++ b/src/org/lineageos/updater/controller/UpdaterService.java @@ -74,6 +74,7 @@ public class UpdaterService extends Service { private static final int NOTIFICATION_ID = 10; private final IBinder mBinder = new LocalBinder(); + private static boolean isNetworkCallBackActive = false; private boolean mHasClients; private BroadcastReceiver mBroadcastReceiver; @@ -249,7 +250,7 @@ public class UpdaterService extends Service { private void tryStopSelf() { if (!mHasClients && !mUpdaterController.hasActiveDownloads() && - !mUpdaterController.isInstallingUpdate()) { + !mUpdaterController.isInstallingUpdate() && !isNetworkCallBackActive()) { Log.d(TAG, "Service no longer needed, stopping"); stopSelf(); } @@ -473,6 +474,12 @@ public class UpdaterService extends Service { } } + public static void setNetworkCallBackActive(boolean isActive) { + isNetworkCallBackActive = isActive; + } + + public static boolean isNetworkCallBackActive() { return isNetworkCallBackActive; } + private void handleDownloadProgressChange(UpdateInfo update) { int progress = update.getProgress(); mNotificationBuilder.setProgress(100, progress, false); diff --git a/src/org/lineageos/updater/misc/ConnectionStateMonitor.kt b/src/org/lineageos/updater/misc/ConnectionStateMonitor.kt new file mode 100644 index 0000000000000000000000000000000000000000..583d770c55e1f6bb018f99165ad961c5215261be --- /dev/null +++ b/src/org/lineageos/updater/misc/ConnectionStateMonitor.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 MURENA SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lineageos.updater.misc + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.net.Network +import android.os.Handler +import android.os.Looper +import android.util.Log +import org.lineageos.updater.UpdatesCheckReceiver + +class ConnectionStateMonitor { + companion object { + private var instance: ConnectivityManager.NetworkCallback? = null + } + + fun getInstance(context: Context): ConnectivityManager.NetworkCallback { + if (instance == null) { + instance = networkCallback(context) + } + + return instance!! + } + + /** + * API callbacks to determine which status we currently in + * we need the below callbacks: + * - onAvailable: device connected to a network of course + * - onLost: when the connection completely lost + */ + fun networkCallback(context: Context) = object: ConnectivityManager.NetworkCallback() { + private val tag = "ConnectionStateMonitor" + private val delayExecute = 10000L // 10 seconds + + private fun startUpdatesCheckReceiver(): Runnable = Runnable { + val broadcastIntent = Intent() + broadcastIntent.setClassName(context, UpdatesCheckReceiver::class.java.name) + context.sendBroadcast(broadcastIntent) + } + + private fun checkForUpdates() { + // Delay because some phones takes time go online + Handler(Looper.getMainLooper()).postDelayed( + startUpdatesCheckReceiver(), delayExecute + ) + } + + override fun onAvailable(network: Network) { + Log.d(tag, "Network available") + checkForUpdates() + } + + override fun onLost(network: Network) { + Log.d(tag, "Network not available") + checkForUpdates() + } + } +} diff --git a/src/org/lineageos/updater/misc/Constants.java b/src/org/lineageos/updater/misc/Constants.java index 96c0b015391a920a274739e2f5d51cb539bf07bd..6a0f02c3ec819a8b81e94dafe1068f24d9191f3d 100644 --- a/src/org/lineageos/updater/misc/Constants.java +++ b/src/org/lineageos/updater/misc/Constants.java @@ -27,6 +27,9 @@ public final class Constants { public static final int AUTO_UPDATES_CHECK_INTERVAL_DAILY = 1; public static final int AUTO_UPDATES_CHECK_INTERVAL_WEEKLY = 2; public static final int AUTO_UPDATES_CHECK_INTERVAL_MONTHLY = 3; + public static final int AUTO_UPDATES_CHECK_INTERVAL_5_MINUTES = 4; + public static final int AUTO_UPDATES_CHECK_INTERVAL_10_MINUTES = 5; + public static final int AUTO_UPDATES_CHECK_INTERVAL_30_MINUTES = 6; public static final String PREF_LAST_UPDATE_CHECK = "last_update_check"; public static final String PREF_AUTO_UPDATES_CHECK_INTERVAL = "auto_updates_check_interval"; @@ -34,6 +37,7 @@ public final class Constants { public static final String PREF_AB_PERF_MODE = "ab_perf_mode"; public static final String PREF_MOBILE_DATA_WARNING = "pref_mobile_data_warning"; public static final String PREF_NEEDS_REBOOT_ID = "needs_reboot_id"; + public static final String PREF_NETWORK_CALLBACK_ACTIVE = "pref_network_callback_active"; public static final String UNCRYPT_FILE_EXT = ".uncrypt"; diff --git a/src/org/lineageos/updater/misc/Utils.java b/src/org/lineageos/updater/misc/Utils.java index c33bb0813db955552edd618765c567fcda85b0e5..ec40230f66d03e3570a0e4dd137788390c43c055 100644 --- a/src/org/lineageos/updater/misc/Utils.java +++ b/src/org/lineageos/updater/misc/Utils.java @@ -39,6 +39,7 @@ import android.os.PersistableBundle; import android.os.SystemProperties; import android.os.SystemUpdateManager; import android.os.storage.StorageManager; +import android.provider.Settings; import android.util.Log; import android.widget.Toast; @@ -456,6 +457,12 @@ public class Utils { return isAB; } + public static boolean isDevModeOn(Context context) { + int devOptions = Settings.Secure.getInt(context.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0); + return devOptions == 1; + } + public static boolean hasTouchscreen(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); } @@ -486,6 +493,12 @@ public class Utils { public static long getUpdateCheckInterval(Context context) { switch (Utils.getUpdateCheckSetting(context)) { + case Constants.AUTO_UPDATES_CHECK_INTERVAL_5_MINUTES: + return AlarmManager.INTERVAL_FIFTEEN_MINUTES / 3; + case Constants.AUTO_UPDATES_CHECK_INTERVAL_10_MINUTES: + return AlarmManager.INTERVAL_HALF_HOUR / 3; + case Constants.AUTO_UPDATES_CHECK_INTERVAL_30_MINUTES: + return AlarmManager.INTERVAL_HALF_HOUR; case Constants.AUTO_UPDATES_CHECK_INTERVAL_DAILY: return AlarmManager.INTERVAL_DAY; case Constants.AUTO_UPDATES_CHECK_INTERVAL_WEEKLY: