Loading core/res/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -3188,6 +3188,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.backup.KeyValueBackupJob" android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.pm.BackgroundDexOptService" android:exported="true" Loading services/backup/java/com/android/server/backup/BackupManagerService.java +27 −49 Original line number Diff line number Diff line Loading @@ -157,9 +157,9 @@ import libcore.io.IoUtils; public class BackupManagerService { private static final String TAG = "BackupManagerService"; private static final boolean DEBUG = true; private static final boolean MORE_DEBUG = false; private static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true; static final boolean DEBUG = true; static final boolean MORE_DEBUG = false; static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true; // System-private key used for backing up an app's widget state. Must // begin with U+FFxx by convention (we reserve all keys starting Loading Loading @@ -195,17 +195,6 @@ public class BackupManagerService { static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup"; static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR; // Random variation in backup scheduling time to avoid server load spikes private static final int FUZZ_MILLIS = 5 * 60 * 1000; // The amount of time between the initial provisioning of the device and // the first backup pass. private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR; // Retry interval for clear/init when the transport is unavailable private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR; Loading Loading @@ -299,7 +288,6 @@ public class BackupManagerService { volatile boolean mBackupRunning; volatile boolean mConnecting; volatile long mLastBackupPass; volatile long mNextBackupPass; // For debugging, we maintain a progress trace of operations during backup static final boolean DEBUG_BACKUP_TRACE = true; Loading Loading @@ -381,7 +369,7 @@ public class BackupManagerService { if (mProvisioned && !wasProvisioned && mEnabled) { // we're now good to go, so start the backup alarms if (MORE_DEBUG) Slog.d(TAG, "Now provisioned, so starting backups"); startBackupAlarmsLocked(FIRST_BACKUP_INTERVAL); KeyValueBackupJob.schedule(mContext); scheduleNextFullBackupJob(); } } Loading Loading @@ -657,7 +645,6 @@ public class BackupManagerService { case MSG_RUN_BACKUP: { mLastBackupPass = System.currentTimeMillis(); mNextBackupPass = mLastBackupPass + BACKUP_INTERVAL; IBackupTransport transport = getTransport(mCurrentTransport); if (transport == null) { Loading Loading @@ -2939,12 +2926,22 @@ public class BackupManagerService { void revertAndEndBackup() { if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything"); addBackupTrace("transport error; reverting"); // We want to reset the backup schedule based on whatever the transport suggests // by way of retry/backoff time. long delay; try { delay = mTransport.requestBackupTime(); } catch (Exception e) { Slog.w(TAG, "Unable to contact transport for recommended backoff"); delay = 0; // use the scheduler's default } KeyValueBackupJob.schedule(mContext, delay); for (BackupRequest request : mOriginalQueue) { dataChangedImpl(request.packageName); } // We also want to reset the backup schedule based on whatever // the transport suggests by way of retry/backoff time. restartBackupAlarm(); } void agentErrorCleanup() { Loading Loading @@ -2977,15 +2974,6 @@ public class BackupManagerService { } } void restartBackupAlarm() { addBackupTrace("setting backup trigger"); synchronized (mQueueLock) { try { startBackupAlarmsLocked(mTransport.requestBackupTime()); } catch (RemoteException e) { /* cannot happen */ } } } void executeNextState(BackupState nextState) { if (MORE_DEBUG) Slog.i(TAG, " => executing next step on " + this + " nextState=" + nextState); Loading Loading @@ -8111,6 +8099,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } } // ...and schedule a backup pass if necessary KeyValueBackupJob.schedule(mContext); } // Note: packageName is currently unused, but may be in the future Loading Loading @@ -8245,16 +8236,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { // Because the alarms we are using can jitter, and we want an *immediate* // backup pass to happen, we restart the timer beginning with "next time," // then manually fire the backup trigger intent ourselves. startBackupAlarmsLocked(BACKUP_INTERVAL); // Fire the intent that kicks off the whole shebang... try { mRunBackupIntent.send(); } catch (PendingIntent.CanceledException e) { // should never happen Slog.e(TAG, "run-backup intent cancelled!"); } // ...and cancel any pending scheduled job, because we've just superseded it KeyValueBackupJob.cancel(mContext); } } Loading Loading @@ -8530,13 +8521,13 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF synchronized (mQueueLock) { if (enable && !wasEnabled && mProvisioned) { // if we've just been enabled, start scheduling backup passes startBackupAlarmsLocked(BACKUP_INTERVAL); KeyValueBackupJob.schedule(mContext); scheduleNextFullBackupJob(); } else if (!enable) { // No longer enabled, so stop running backups if (DEBUG) Slog.i(TAG, "Opting out of backup"); mAlarmManager.cancel(mRunBackupIntent); KeyValueBackupJob.cancel(mContext); // This also constitutes an opt-out, so we wipe any data for // this device from the backend. We start that process with Loading Loading @@ -8590,19 +8581,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF */ } private void startBackupAlarmsLocked(long delayBeforeFirstBackup) { // We used to use setInexactRepeating(), but that may be linked to // backups running at :00 more often than not, creating load spikes. // Schedule at an exact time for now, and also add a bit of "fuzz". Random random = new Random(); long when = System.currentTimeMillis() + delayBeforeFirstBackup + random.nextInt(FUZZ_MILLIS); mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, when, BACKUP_INTERVAL + random.nextInt(FUZZ_MILLIS), mRunBackupIntent); mNextBackupPass = when; } // Report whether the backup mechanism is currently enabled public boolean isBackupEnabled() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "isBackupEnabled"); Loading Loading @@ -9308,7 +9286,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (mBackupRunning) pw.println("Backup currently running"); pw.println("Last backup pass started: " + mLastBackupPass + " (now = " + System.currentTimeMillis() + ')'); pw.println(" next scheduled: " + mNextBackupPass); pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled()); pw.println("Available transports:"); final String[] transports = listAllTransports(); Loading services/backup/java/com/android/server/backup/KeyValueBackupJob.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * 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 com.android.server.backup; import android.app.AlarmManager; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.os.RemoteException; import android.util.Slog; import java.util.Random; /** * Job for scheduling key/value backup work. This module encapsulates all * of the policy around when those backup passes are executed. */ public class KeyValueBackupJob extends JobService { private static final String TAG = "KeyValueBackupJob"; private static ComponentName sKeyValueJobService = new ComponentName("android", KeyValueBackupJob.class.getName()); private static final int JOB_ID = 0x5039; // Once someone asks for a backup, this is how long we hold off, batching // up additional requests, before running the actual backup pass. Privileged // callers can always trigger an immediate pass via BackupManager.backupNow(). private static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR; // Random variation in next-backup scheduling time to avoid server load spikes private static final int FUZZ_MILLIS = 10 * 60 * 1000; // Don't let the job scheduler defer forever; give it a (lenient) deadline private static final long MAX_DEFERRAL = 1 * AlarmManager.INTERVAL_HOUR; private static boolean sScheduled = false; private static long sNextScheduled = 0; public static void schedule(Context ctx) { schedule(ctx, 0); } public static void schedule(Context ctx, long delay) { synchronized (KeyValueBackupJob.class) { if (!sScheduled) { if (delay <= 0) { delay = BATCH_INTERVAL + new Random().nextInt(FUZZ_MILLIS); } if (BackupManagerService.DEBUG_SCHEDULING) { Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes"); } JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService) .setMinimumLatency(delay) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .setOverrideDeadline(delay + MAX_DEFERRAL); js.schedule(builder.build()); sNextScheduled = System.currentTimeMillis() + delay; sScheduled = true; } } } public static void cancel(Context ctx) { synchronized (KeyValueBackupJob.class) { JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); js.cancel(JOB_ID); sNextScheduled = 0; sScheduled = false; } } public static long nextScheduled() { synchronized (KeyValueBackupJob.class) { return sNextScheduled; } } @Override public boolean onStartJob(JobParameters params) { synchronized (KeyValueBackupJob.class) { sNextScheduled = 0; sScheduled = false; } // Time to run a key/value backup! Trampoline service = BackupManagerService.getInstance(); try { service.backupNow(); } catch (RemoteException e) {} // This was just a trigger; ongoing wakelock management is done by the // rest of the backup system. return false; } @Override public boolean onStopJob(JobParameters params) { // Intentionally empty; the job starting was just a trigger return false; } } Loading
core/res/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -3188,6 +3188,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.backup.KeyValueBackupJob" android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.pm.BackgroundDexOptService" android:exported="true" Loading
services/backup/java/com/android/server/backup/BackupManagerService.java +27 −49 Original line number Diff line number Diff line Loading @@ -157,9 +157,9 @@ import libcore.io.IoUtils; public class BackupManagerService { private static final String TAG = "BackupManagerService"; private static final boolean DEBUG = true; private static final boolean MORE_DEBUG = false; private static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true; static final boolean DEBUG = true; static final boolean MORE_DEBUG = false; static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true; // System-private key used for backing up an app's widget state. Must // begin with U+FFxx by convention (we reserve all keys starting Loading Loading @@ -195,17 +195,6 @@ public class BackupManagerService { static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup"; static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR; // Random variation in backup scheduling time to avoid server load spikes private static final int FUZZ_MILLIS = 5 * 60 * 1000; // The amount of time between the initial provisioning of the device and // the first backup pass. private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR; // Retry interval for clear/init when the transport is unavailable private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR; Loading Loading @@ -299,7 +288,6 @@ public class BackupManagerService { volatile boolean mBackupRunning; volatile boolean mConnecting; volatile long mLastBackupPass; volatile long mNextBackupPass; // For debugging, we maintain a progress trace of operations during backup static final boolean DEBUG_BACKUP_TRACE = true; Loading Loading @@ -381,7 +369,7 @@ public class BackupManagerService { if (mProvisioned && !wasProvisioned && mEnabled) { // we're now good to go, so start the backup alarms if (MORE_DEBUG) Slog.d(TAG, "Now provisioned, so starting backups"); startBackupAlarmsLocked(FIRST_BACKUP_INTERVAL); KeyValueBackupJob.schedule(mContext); scheduleNextFullBackupJob(); } } Loading Loading @@ -657,7 +645,6 @@ public class BackupManagerService { case MSG_RUN_BACKUP: { mLastBackupPass = System.currentTimeMillis(); mNextBackupPass = mLastBackupPass + BACKUP_INTERVAL; IBackupTransport transport = getTransport(mCurrentTransport); if (transport == null) { Loading Loading @@ -2939,12 +2926,22 @@ public class BackupManagerService { void revertAndEndBackup() { if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything"); addBackupTrace("transport error; reverting"); // We want to reset the backup schedule based on whatever the transport suggests // by way of retry/backoff time. long delay; try { delay = mTransport.requestBackupTime(); } catch (Exception e) { Slog.w(TAG, "Unable to contact transport for recommended backoff"); delay = 0; // use the scheduler's default } KeyValueBackupJob.schedule(mContext, delay); for (BackupRequest request : mOriginalQueue) { dataChangedImpl(request.packageName); } // We also want to reset the backup schedule based on whatever // the transport suggests by way of retry/backoff time. restartBackupAlarm(); } void agentErrorCleanup() { Loading Loading @@ -2977,15 +2974,6 @@ public class BackupManagerService { } } void restartBackupAlarm() { addBackupTrace("setting backup trigger"); synchronized (mQueueLock) { try { startBackupAlarmsLocked(mTransport.requestBackupTime()); } catch (RemoteException e) { /* cannot happen */ } } } void executeNextState(BackupState nextState) { if (MORE_DEBUG) Slog.i(TAG, " => executing next step on " + this + " nextState=" + nextState); Loading Loading @@ -8111,6 +8099,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } } // ...and schedule a backup pass if necessary KeyValueBackupJob.schedule(mContext); } // Note: packageName is currently unused, but may be in the future Loading Loading @@ -8245,16 +8236,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { // Because the alarms we are using can jitter, and we want an *immediate* // backup pass to happen, we restart the timer beginning with "next time," // then manually fire the backup trigger intent ourselves. startBackupAlarmsLocked(BACKUP_INTERVAL); // Fire the intent that kicks off the whole shebang... try { mRunBackupIntent.send(); } catch (PendingIntent.CanceledException e) { // should never happen Slog.e(TAG, "run-backup intent cancelled!"); } // ...and cancel any pending scheduled job, because we've just superseded it KeyValueBackupJob.cancel(mContext); } } Loading Loading @@ -8530,13 +8521,13 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF synchronized (mQueueLock) { if (enable && !wasEnabled && mProvisioned) { // if we've just been enabled, start scheduling backup passes startBackupAlarmsLocked(BACKUP_INTERVAL); KeyValueBackupJob.schedule(mContext); scheduleNextFullBackupJob(); } else if (!enable) { // No longer enabled, so stop running backups if (DEBUG) Slog.i(TAG, "Opting out of backup"); mAlarmManager.cancel(mRunBackupIntent); KeyValueBackupJob.cancel(mContext); // This also constitutes an opt-out, so we wipe any data for // this device from the backend. We start that process with Loading Loading @@ -8590,19 +8581,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF */ } private void startBackupAlarmsLocked(long delayBeforeFirstBackup) { // We used to use setInexactRepeating(), but that may be linked to // backups running at :00 more often than not, creating load spikes. // Schedule at an exact time for now, and also add a bit of "fuzz". Random random = new Random(); long when = System.currentTimeMillis() + delayBeforeFirstBackup + random.nextInt(FUZZ_MILLIS); mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, when, BACKUP_INTERVAL + random.nextInt(FUZZ_MILLIS), mRunBackupIntent); mNextBackupPass = when; } // Report whether the backup mechanism is currently enabled public boolean isBackupEnabled() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "isBackupEnabled"); Loading Loading @@ -9308,7 +9286,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (mBackupRunning) pw.println("Backup currently running"); pw.println("Last backup pass started: " + mLastBackupPass + " (now = " + System.currentTimeMillis() + ')'); pw.println(" next scheduled: " + mNextBackupPass); pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled()); pw.println("Available transports:"); final String[] transports = listAllTransports(); Loading
services/backup/java/com/android/server/backup/KeyValueBackupJob.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * 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 com.android.server.backup; import android.app.AlarmManager; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.os.RemoteException; import android.util.Slog; import java.util.Random; /** * Job for scheduling key/value backup work. This module encapsulates all * of the policy around when those backup passes are executed. */ public class KeyValueBackupJob extends JobService { private static final String TAG = "KeyValueBackupJob"; private static ComponentName sKeyValueJobService = new ComponentName("android", KeyValueBackupJob.class.getName()); private static final int JOB_ID = 0x5039; // Once someone asks for a backup, this is how long we hold off, batching // up additional requests, before running the actual backup pass. Privileged // callers can always trigger an immediate pass via BackupManager.backupNow(). private static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR; // Random variation in next-backup scheduling time to avoid server load spikes private static final int FUZZ_MILLIS = 10 * 60 * 1000; // Don't let the job scheduler defer forever; give it a (lenient) deadline private static final long MAX_DEFERRAL = 1 * AlarmManager.INTERVAL_HOUR; private static boolean sScheduled = false; private static long sNextScheduled = 0; public static void schedule(Context ctx) { schedule(ctx, 0); } public static void schedule(Context ctx, long delay) { synchronized (KeyValueBackupJob.class) { if (!sScheduled) { if (delay <= 0) { delay = BATCH_INTERVAL + new Random().nextInt(FUZZ_MILLIS); } if (BackupManagerService.DEBUG_SCHEDULING) { Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes"); } JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService) .setMinimumLatency(delay) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .setOverrideDeadline(delay + MAX_DEFERRAL); js.schedule(builder.build()); sNextScheduled = System.currentTimeMillis() + delay; sScheduled = true; } } } public static void cancel(Context ctx) { synchronized (KeyValueBackupJob.class) { JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); js.cancel(JOB_ID); sNextScheduled = 0; sScheduled = false; } } public static long nextScheduled() { synchronized (KeyValueBackupJob.class) { return sNextScheduled; } } @Override public boolean onStartJob(JobParameters params) { synchronized (KeyValueBackupJob.class) { sNextScheduled = 0; sScheduled = false; } // Time to run a key/value backup! Trampoline service = BackupManagerService.getInstance(); try { service.backupNow(); } catch (RemoteException e) {} // This was just a trigger; ongoing wakelock management is done by the // rest of the backup system. return false; } @Override public boolean onStopJob(JobParameters params) { // Intentionally empty; the job starting was just a trigger return false; } }