diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f1122ec..567ed1b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -60,6 +60,10 @@ android:directBootAware="true" android:exported="false" /> + + 3 2 + + + @string/check_frequency_daily + @string/check_frequency_weekly + @string/check_frequency_biweekly + @string/check_frequency_monthly + @string/check_frequency_manual + + + + 86400000 + 604800000 + 1209600000 + 2592000000 + 0 + + + + @string/update_behavior_auto_install + @string/update_behavior_auto_download + @string/update_behavior_notify + + + + 0 + 1 + 2 + diff --git a/res/values/config.xml b/res/values/config.xml index b3804bb..1b43027 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -6,4 +6,6 @@ true false false + 86400000 + 0 diff --git a/res/values/strings.xml b/res/values/strings.xml index c005ade..dd937ff 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -25,6 +25,14 @@ The latest update downloaded from the server failed to pass verification. The update system will try again soon. The download will be performed again. Failed to install update The latest update passed verification but installation failed. The update system will try again soon. + Update available + System update available + A new OS release is available. Tap to download and install it. + Download & install + Update ready to install + System update ready to install + A new OS release has been downloaded and verified. Tap to install it. + Install Security preview releases Tap to choose whether to opt into security preview releases System update @@ -33,6 +41,16 @@ Stable Beta Alpha + Update check frequency + Daily + Weekly + Every two weeks + Monthly + Manual only + Update behavior + Automatically download and install + Automatically download, notify to install manually + Only notify when an update is available Permitted networks Any Not roaming diff --git a/res/xml/settings.xml b/res/xml/settings.xml index 43517af..df0b106 100644 --- a/res/xml/settings.xml +++ b/res/xml/settings.xml @@ -10,6 +10,22 @@ app:allowDividerBelow="false" app:iconSpaceReserved="false"> + + + + { @@ -241,6 +243,12 @@ private void onDownloadFinished(final boolean streaming, final long targetBuildD try (final var propertiesReader = new BufferedReader(new InputStreamReader(zipFile.getInputStream(payloadProperties)))) { headerKeyValuePairs = propertiesReader.lines().toArray(String[]::new); } + if (!installAfterVerify) { + Log.d(TAG, "update downloaded and verified; notifying user to install manually"); + notificationHandler.showUpdateDownloadedNotification(); + mUpdating = false; + return; + } applyUpdate(streaming, payloadOffset, headerKeyValuePairs, sourceIncremental != null); } catch (final GeneralSecurityException e) { UPDATE_PATH.delete(); @@ -265,6 +273,8 @@ protected void onHandleIntent(final Intent intent) { final Network network = intent.getParcelableExtra(INTENT_EXTRA_NETWORK, Network.class); final var serviceIsUserInitiated = intent.getBooleanExtra(INTENT_EXTRA_IS_USER_INITIATED, false); if (serviceIsUserInitiated) Log.d(TAG, "onHandleIntent() – service is user-initiated"); + final var userRequestedInstall = intent.getBooleanExtra(INTENT_EXTRA_USER_REQUESTED_INSTALL, false); + if (userRequestedInstall) Log.d(TAG, "onHandleIntent() – install requested by user"); final PowerManager pm = getSystemService(PowerManager.class); final WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Updater:" + TAG); @@ -316,6 +326,16 @@ protected void onHandleIntent(final Intent intent) { throw new GeneralSecurityException("targetChannel: " + targetChannel + " does not match channel: " + channel); } + final int updateBehavior = Settings.getUpdateBehavior(this); + if (updateBehavior == Settings.UPDATE_BEHAVIOR_NOTIFY && !userRequestedInstall) { + Log.d(TAG, "update available; only notifying per update behavior setting"); + notificationHandler.showUpdateAvailableNotification(); + mUpdating = false; + return; + } + final boolean installAfterVerify = userRequestedInstall + || updateBehavior == Settings.UPDATE_BEHAVIOR_AUTO_INSTALL; + notificationHandler.showDownloadNotification(0, 100); String downloadFile = preferences.getString(PREFERENCE_DOWNLOAD_FILE, null); @@ -341,7 +361,7 @@ protected void onHandleIntent(final Intent intent) { final int responseCode = connection.getResponseCode(); if (responseCode == HTTP_RANGE_NOT_SATISFIABLE) { Log.d(TAG, "download completed previously"); - onDownloadFinished(streaming, targetBuildDate, targetIncremental, channel); + onDownloadFinished(streaming, targetBuildDate, targetIncremental, channel, installAfterVerify); return; } if (responseCode == HTTP_NOT_FOUND && incrementalUpdate.equals(downloadFile)) { @@ -418,7 +438,7 @@ protected void onHandleIntent(final Intent intent) { } Log.d(TAG, "download completed"); - onDownloadFinished(streaming, targetBuildDate, targetIncremental, channel); + onDownloadFinished(streaming, targetBuildDate, targetIncremental, channel, installAfterVerify); } catch (GeneralSecurityException | IOException | ServiceSpecificException e) { Log.e(TAG, "failed to download and install update", e); notificationHandler.showFailureNotification(e.getMessage()); diff --git a/src/app/seamlessupdate/client/Settings.java b/src/app/seamlessupdate/client/Settings.java index aa456ba..a756cf4 100644 --- a/src/app/seamlessupdate/client/Settings.java +++ b/src/app/seamlessupdate/client/Settings.java @@ -30,8 +30,14 @@ public class Settings extends CollapsingToolbarBaseActivity { private static final String KEY_REQUIRES_CHARGING = "requires_charging"; private static final String KEY_IDLE_REBOOT = "idle_reboot"; private static final String KEY_CHECK_FOR_UPDATES = "check_for_updates"; + private static final String KEY_CHECK_FREQUENCY = "check_frequency"; + private static final String KEY_UPDATE_BEHAVIOR = "update_behavior"; static final String KEY_WAITING_FOR_REBOOT = "waiting_for_reboot"; + static final int UPDATE_BEHAVIOR_AUTO_INSTALL = 0; + static final int UPDATE_BEHAVIOR_AUTO_DOWNLOAD = 1; + static final int UPDATE_BEHAVIOR_NOTIFY = 2; + static SharedPreferences getPreferences(final Context context) { final Context deviceContext = context.createDeviceProtectedStorageContext(); return PreferenceManager.getDefaultSharedPreferences(deviceContext); @@ -74,6 +80,16 @@ static boolean getRequiresCharging(final Context context) { Boolean.parseBoolean(context.getString(R.string.requires_charging_default))); } + static long getCheckFrequency(final Context context) { + return Long.parseLong(getPreferences(context).getString(KEY_CHECK_FREQUENCY, + context.getString(R.string.check_frequency_default))); + } + + static int getUpdateBehavior(final Context context) { + return Integer.parseInt(getPreferences(context).getString(KEY_UPDATE_BEHAVIOR, + context.getString(R.string.update_behavior_default))); + } + static boolean getIdleReboot(final Context context) { return getPreferences(context).getBoolean(KEY_IDLE_REBOOT, Boolean.parseBoolean(context.getString(R.string.idle_reboot_default))); @@ -164,6 +180,7 @@ private TwoStatePreference updateAndReturnSecurityPreviewPreference() { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { switch (key) { case KEY_CHANNEL: + case KEY_CHECK_FREQUENCY: case KEY_BATTERY_NOT_LOW: case KEY_REQUIRES_CHARGING: if (!getPreferences(requireContext()).getBoolean(KEY_WAITING_FOR_REBOOT, false)) {