Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
android:directBootAware="true"
android:exported="false" />

<receiver android:name=".InstallReceiver"
android:directBootAware="true"
android:exported="false" />

<activity android:name=".Settings"
android:launchMode="singleTop"
android:label="@string/settings_title"
Expand Down
28 changes: 28 additions & 0 deletions res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,32 @@
<item>3</item>
<item>2</item>
</string-array>

<string-array name="check_frequency_entries" translatable="false">
<item>@string/check_frequency_daily</item>
<item>@string/check_frequency_weekly</item>
<item>@string/check_frequency_biweekly</item>
<item>@string/check_frequency_monthly</item>
<item>@string/check_frequency_manual</item>
</string-array>

<string-array name="check_frequency_values" translatable="false">
<item>86400000</item>
<item>604800000</item>
<item>1209600000</item>
<item>2592000000</item>
Comment thread
misakazip marked this conversation as resolved.
<item>0</item>
</string-array>

<string-array name="update_behavior_entries" translatable="false">
<item>@string/update_behavior_auto_install</item>
<item>@string/update_behavior_auto_download</item>
<item>@string/update_behavior_notify</item>
</string-array>

<string-array name="update_behavior_values" translatable="false">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources>
2 changes: 2 additions & 0 deletions res/values/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
<string name="battery_not_low_default" translatable="false">true</string>
<string name="requires_charging_default" translatable="false">false</string>
<string name="idle_reboot_default" translatable="false">false</string>
<string name="check_frequency_default" translatable="false">86400000</string>
<string name="update_behavior_default" translatable="false">0</string>
</resources>
18 changes: 18 additions & 0 deletions res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
<string name="notification_failed_verify_text">The latest update downloaded from the server failed to pass verification. The update system will try again soon. The download will be performed again.</string>
<string name="notification_failed_install_title">Failed to install update</string>
<string name="notification_failed_install_text">The latest update passed verification but installation failed. The update system will try again soon.</string>
<string name="notification_channel_available">Update available</string>
<string name="notification_available_title">System update available</string>
<string name="notification_available_text">A new OS release is available. Tap to download and install it.</string>
<string name="notification_available_action">Download &amp; install</string>
<string name="notification_channel_downloaded">Update ready to install</string>
<string name="notification_downloaded_title">System update ready to install</string>
<string name="notification_downloaded_text">A new OS release has been downloaded and verified. Tap to install it.</string>
<string name="notification_downloaded_action">Install</string>
<string name="notification_set_security_preview_title">Security preview releases</string>
<string name="notification_set_security_preview_text">Tap to choose whether to opt into security preview releases</string>
<string name="settings_title">System update</string>
Expand All @@ -33,6 +41,16 @@
<string name="channel_stable">Stable</string>
<string name="channel_beta">Beta</string>
<string name="channel_alpha">Alpha</string>
<string name="check_frequency_title">Update check frequency</string>
<string name="check_frequency_daily">Daily</string>
<string name="check_frequency_weekly">Weekly</string>
<string name="check_frequency_biweekly">Every two weeks</string>
<string name="check_frequency_monthly">Monthly</string>
<string name="check_frequency_manual">Manual only</string>
<string name="update_behavior_title">Update behavior</string>
<string name="update_behavior_auto_install">Automatically download and install</string>
<string name="update_behavior_auto_download">Automatically download, notify to install manually</string>
<string name="update_behavior_notify">Only notify when an update is available</string>
<string name="network_type_title">Permitted networks</string>
<string name="network_type_any">Any</string>
<string name="network_type_not_roaming">Not roaming</string>
Expand Down
16 changes: 16 additions & 0 deletions res/xml/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@
app:allowDividerBelow="false"
app:iconSpaceReserved="false">

<ListPreference app:key="check_frequency"
app:title="@string/check_frequency_title"
app:iconSpaceReserved="false"
app:useSimpleSummaryProvider="true"
app:entries="@array/check_frequency_entries"
app:entryValues="@array/check_frequency_values"
app:defaultValue="@string/check_frequency_default" />

<ListPreference app:key="update_behavior"
app:title="@string/update_behavior_title"
app:iconSpaceReserved="false"
app:useSimpleSummaryProvider="true"
app:entries="@array/update_behavior_entries"
app:entryValues="@array/update_behavior_values"
app:defaultValue="@string/update_behavior_default" />

<ListPreference app:key="channel"
app:title="@string/channel_title"
app:iconSpaceReserved="false"
Expand Down
21 changes: 21 additions & 0 deletions src/app/seamlessupdate/client/InstallReceiver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package app.seamlessupdate.client;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;

public class InstallReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
final ConnectivityManager connectivityManager =
context.getSystemService(ConnectivityManager.class);
final Network network = connectivityManager.getActiveNetwork();
final Intent service = new Intent(context, Service.class);
service.putExtra(Service.INTENT_EXTRA_NETWORK, network);
service.putExtra(Service.INTENT_EXTRA_USER_REQUESTED_INSTALL, true);
service.putExtra(Service.INTENT_EXTRA_IS_USER_INITIATED, true);
context.startForegroundService(service);
}
}
60 changes: 59 additions & 1 deletion src/app/seamlessupdate/client/NotificationHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@ private static enum Phase {
private static final int NOTIFICATION_ID_REBOOT = 2;
private static final int NOTIFICATION_ID_FAILURE = 3;
private static final int NOTIFICATION_ID_UPDATED = 4;
private static final int MAX_NOTIFICATION_ID_FOR_SERVICE = NOTIFICATION_ID_UPDATED;
private static final int NOTIFICATION_ID_AVAILABLE = 5;
private static final int NOTIFICATION_ID_DOWNLOADED = 6;
private static final int MAX_NOTIFICATION_ID_FOR_SERVICE = NOTIFICATION_ID_DOWNLOADED;
private static final int NOTIFICATION_ID_SET_SECURITY_PREVIEW = 1000;
private static final String NOTIFICATION_CHANNEL_ID_PROGRESS = "progress";
private static final String NOTIFICATION_CHANNEL_ID_REBOOT = "updates2";
private static final String NOTIFICATION_CHANNEL_ID_FAILURE = "failure";
private static final String NOTIFICATION_CHANNEL_ID_UPDATED = "updated";
private static final String NOTIFICATION_CHANNEL_ID_AVAILABLE = "available";
private static final String NOTIFICATION_CHANNEL_ID_DOWNLOADED = "downloaded";
private static final String NOTIFICATION_CHANNEL_ID_SET_SECURITY_PREVIEW = "set_security_preview";
private static final int PENDING_REBOOT_ID = 1;
private static final int PENDING_SETTINGS_ID = 2;
private static final int PENDING_SECURITY_PREVIEW_SETTINGS_ID = 3;
private static final int PENDING_INSTALL_ID = 4;

private final Service service;
private final NotificationManager notificationManager;
Expand Down Expand Up @@ -77,6 +82,20 @@ static void createNotificationChannels(Context context) {
updated.setBlockable(true);
channels.add(updated);

final NotificationChannel available = new NotificationChannel(NOTIFICATION_CHANNEL_ID_AVAILABLE,
context.getString(R.string.notification_channel_available), IMPORTANCE_HIGH);
available.enableLights(true);
available.enableVibration(true);
available.setBlockable(true);
channels.add(available);

final NotificationChannel downloaded = new NotificationChannel(NOTIFICATION_CHANNEL_ID_DOWNLOADED,
context.getString(R.string.notification_channel_downloaded), IMPORTANCE_HIGH);
downloaded.enableLights(true);
downloaded.enableVibration(true);
downloaded.setBlockable(true);
channels.add(downloaded);

final NotificationChannel setSecurityPreview = new NotificationChannel(NOTIFICATION_CHANNEL_ID_SET_SECURITY_PREVIEW,
context.getString(R.string.notification_channel_set_security_preview), IMPORTANCE_HIGH);
setSecurityPreview.setShowBadge(false);
Expand Down Expand Up @@ -195,6 +214,45 @@ void showRebootNotification() {
.build());
}

private PendingIntent getPendingInstallIntent() {
return PendingIntent.getBroadcast(service, PENDING_INSTALL_ID,
new Intent(service, InstallReceiver.class), PendingIntent.FLAG_IMMUTABLE);
}

void showUpdateAvailableNotification() {
final Notification.Action action = new Notification.Action.Builder(
Icon.createWithResource(service.getApplication(), R.drawable.system_update_fill0_wght400_grad0_opsz48),
service.getString(R.string.notification_available_action),
getPendingInstallIntent()).build();

notificationManager.notify(NOTIFICATION_ID_AVAILABLE, new Notification.Builder(service, NOTIFICATION_CHANNEL_ID_AVAILABLE)
.addAction(action)
.setContentIntent(getPendingInstallIntent())
.setContentTitle(service.getString(R.string.notification_available_title))
.setContentText(service.getString(R.string.notification_available_text))
.setShowWhen(true)
.setTimeoutAfter(-1)
.setSmallIcon(R.drawable.system_update_fill0_wght400_grad0_opsz48)
.build());
}

void showUpdateDownloadedNotification() {
final Notification.Action action = new Notification.Action.Builder(
Icon.createWithResource(service.getApplication(), R.drawable.system_update_fill0_wght400_grad0_opsz48),
service.getString(R.string.notification_downloaded_action),
getPendingInstallIntent()).build();

notificationManager.notify(NOTIFICATION_ID_DOWNLOADED, new Notification.Builder(service, NOTIFICATION_CHANNEL_ID_DOWNLOADED)
.addAction(action)
.setContentIntent(getPendingInstallIntent())
.setContentTitle(service.getString(R.string.notification_downloaded_title))
.setContentText(service.getString(R.string.notification_downloaded_text))
.setShowWhen(true)
.setTimeoutAfter(-1)
.setSmallIcon(R.drawable.system_update_fill0_wght400_grad0_opsz48)
.build());
}
Comment on lines +222 to +254

void showFailureNotification(String exceptionMessage) {
final int titleResId;
final int contentResId;
Expand Down
11 changes: 8 additions & 3 deletions src/app/seamlessupdate/client/PeriodicJob.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class PeriodicJob extends JobService {
private static final String TAG = "PeriodicJob";
private static final int JOB_ID_PERIODIC = 1;
private static final int JOB_ID_RETRY = 2;
private static final long INTERVAL_MILLIS = 6 * 60 * 60 * 1000;
private static final long MIN_LATENCY_MILLIS = 4 * 60 * 1000;
private static final String EXTRA_JOB_CHANNEL = "extra_job_channel";

Expand All @@ -27,14 +26,20 @@ static void schedule(final Context context) {
final int networkType = Settings.getNetworkType(context);
final boolean batteryNotLow = Settings.getBatteryNotLow(context);
final boolean requiresCharging = Settings.getRequiresCharging(context);
final long intervalMillis = Settings.getCheckFrequency(context);
final JobScheduler scheduler = context.getSystemService(JobScheduler.class);
if (intervalMillis <= 0) {
Log.d(TAG, "manual update checks only; cancelling periodic job");
scheduler.cancel(JOB_ID_PERIODIC);
return;
}
final JobInfo jobInfo = scheduler.getPendingJob(JOB_ID_PERIODIC);
if (jobInfo != null &&
jobInfo.getNetworkType() == networkType &&
jobInfo.isRequireBatteryNotLow() == batteryNotLow &&
jobInfo.isRequireCharging() == requiresCharging &&
jobInfo.isPersisted() &&
jobInfo.getIntervalMillis() == INTERVAL_MILLIS &&
jobInfo.getIntervalMillis() == intervalMillis &&
Objects.equals(jobInfo.getExtras().getString(EXTRA_JOB_CHANNEL), channel)) {
Log.d(TAG, "Periodic job already registered");
return;
Expand All @@ -47,7 +52,7 @@ static void schedule(final Context context) {
.setRequiresBatteryNotLow(batteryNotLow)
.setRequiresCharging(requiresCharging)
.setPersisted(true)
.setPeriodic(INTERVAL_MILLIS)
.setPeriodic(intervalMillis)
.setExtras(extras)
.build());
if (result == JobScheduler.RESULT_FAILURE) {
Expand Down
26 changes: 23 additions & 3 deletions src/app/seamlessupdate/client/Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class Service extends IntentService {
private static final String TAG = "Service";
static final String INTENT_EXTRA_NETWORK = "network";
static final String INTENT_EXTRA_IS_USER_INITIATED = "is_user_initiated";
static final String INTENT_EXTRA_USER_REQUESTED_INSTALL = "user_requested_install";
private static final int CONNECT_TIMEOUT = 30000;
private static final int READ_TIMEOUT = 30000;
private static final File CARE_MAP_PATH = new File("/data/ota_package/care_map.pb");
Expand Down Expand Up @@ -149,7 +150,8 @@ private static ZipEntry getEntry(final ZipFile zipFile, final String name) throw
}

private void onDownloadFinished(final boolean streaming, final long targetBuildDate,
final String targetIncremental, final String channel) throws IOException, GeneralSecurityException {
final String targetIncremental, final String channel, final boolean installAfterVerify)
throws IOException, GeneralSecurityException {
try {
notificationHandler.showVerifyNotification(0);
RecoverySystem.verifyPackage(UPDATE_PATH, (int progress) -> {
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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)) {
Expand Down Expand Up @@ -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());
Expand Down
Loading