diff options
author | Makoto Onuki <omakoto@google.com> | 2018-01-31 17:22:36 -0800 |
---|---|---|
committer | Makoto Onuki <omakoto@google.com> | 2018-02-02 16:40:25 -0800 |
commit | 61283ecc7faeabd0556f4509aca7185bc112ce14 (patch) | |
tree | 3df5ca61fab88c3b449140fe45fe0dfb08fcf978 | |
parent | 2ef26bf2dfe203312d6c71a01426191060ff4e46 (diff) |
Exempt sync requests by FG app from app-standby
Test: manual test with request sync, etc
Bug: 72443754
Change-Id: Iecf2d3a8c54451324a02ca2762bda72aa219bd92
8 files changed, 254 insertions, 66 deletions
diff --git a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java index 360b2308b1c7..b76d6694ca2d 100644 --- a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java +++ b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java @@ -19,6 +19,7 @@ package com.android.commands.requestsync; import android.accounts.Account; import android.content.ContentResolver; +import android.content.SyncRequest; import android.os.Bundle; import java.net.URISyntaxException; @@ -28,12 +29,31 @@ public class RequestSync { private String[] mArgs; private int mNextArg; private String mCurArgData; + private boolean mIsForegroundRequest; enum Operation { REQUEST_SYNC { @Override void invoke(RequestSync caller) { - ContentResolver.requestSync(caller.mAccount, caller.mAuthority, caller.mExtras); + if (caller.mIsForegroundRequest) { + caller.mExtras.putBoolean( + ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC, true); + } else { + caller.mExtras.putBoolean( + ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC, true); + System.out.println( + "Making a sync request as a background app.\n" + + "Note: request may be throttled by App Standby.\n" + + "To override this behavior and run a sync immediately," + + " pass a -f option.\n"); + } + final SyncRequest request = + new SyncRequest.Builder() + .setSyncAdapter(caller.mAccount, caller.mAuthority) + .setExtras(caller.mExtras) + .syncOnce() + .build(); + ContentResolver.requestSync(request); } }, ADD_PERIODIC_SYNC { @@ -191,6 +211,10 @@ public class RequestSync { final String key = nextArgRequired(); final String value = nextArgRequired(); mExtras.putBoolean(key, Boolean.valueOf(value)); + + } else if (opt.equals("-f") || opt.equals("--foreground")) { + mIsForegroundRequest = true; + } else { System.err.println("Error: Unknown option: " + opt); showUsage(); @@ -267,6 +291,9 @@ public class RequestSync { " -n|--account-name <ACCOUNT-NAME>\n" + " -t|--account-type <ACCOUNT-TYPE>\n" + " -a|--authority <AUTHORITY>\n" + + " App-standby related options\n" + + "\n" + + " -f|--foreground (Exempt a sync from app standby)\n" + " ContentResolver extra options:\n" + " --is|--ignore-settings: Add SYNC_EXTRAS_IGNORE_SETTINGS\n" + " --ib|--ignore-backoff: Add SYNC_EXTRAS_IGNORE_BACKOFF\n" + diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 8d2e141ae78f..801513cb76de 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -166,6 +166,26 @@ public abstract class ContentResolver { public static final String SYNC_EXTRAS_DISALLOW_METERED = "allow_metered"; /** + * {@hide} Flag only used by the requestsync command to treat a request as if it was made by + * a foreground app. + * + * Only the system and the shell user can set it. + * + * This extra is "virtual". Once passed to the system server, it'll be removed from the bundle. + */ + public static final String SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC = "force_fg_sync"; + + /** + * {@hide} Flag only used by the requestsync command to treat a request as if it was made by + * a background app. + * + * Only the system and the shell user can set it. + * + * This extra is "virtual". Once passed to the system server, it'll be removed from the bundle. + */ + public static final String SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC = "force_bg_sync"; + + /** * Set by the SyncManager to request that the SyncAdapter initialize itself for * the given account/authority pair. One required initialization step is to * ensure that {@link #setIsSyncable(android.accounts.Account, String, int)} has been @@ -2435,13 +2455,7 @@ public abstract class ContentResolver { public static void addPeriodicSync(Account account, String authority, Bundle extras, long pollFrequency) { validateSyncExtrasBundle(extras); - if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false) - || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false) - || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false) - || extras.getBoolean(SYNC_EXTRAS_IGNORE_SETTINGS, false) - || extras.getBoolean(SYNC_EXTRAS_INITIALIZE, false) - || extras.getBoolean(SYNC_EXTRAS_FORCE, false) - || extras.getBoolean(SYNC_EXTRAS_EXPEDITED, false)) { + if (invalidPeriodicExtras(extras)) { throw new IllegalArgumentException("illegal extras were set"); } try { diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 6280edb87a02..1ee0548a5bf7 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -48,8 +48,8 @@ import android.os.Bundle; import android.os.FactoryTest; import android.os.IBinder; import android.os.Parcel; +import android.os.Process; import android.os.RemoteException; -import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; @@ -446,7 +446,7 @@ public final class ContentService extends IContentService.Stub { SyncManager syncManager = getSyncManager(); if (syncManager != null) { syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid, - uri.getAuthority()); + uri.getAuthority(), /*isAppStandbyExempted=*/ isUidInForeground(uid)); } } @@ -502,6 +502,9 @@ public final class ContentService extends IContentService.Stub { int userId = UserHandle.getCallingUserId(); int uId = Binder.getCallingUid(); + validateExtras(uId, extras); + final boolean isForegroundSyncRequest = isForegroundSyncRequest(uId, extras); + // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. long identityToken = clearCallingIdentity(); @@ -509,7 +512,8 @@ public final class ContentService extends IContentService.Stub { SyncManager syncManager = getSyncManager(); if (syncManager != null) { syncManager.scheduleSync(account, userId, uId, authority, extras, - SyncStorageEngine.AuthorityInfo.UNDEFINED); + SyncStorageEngine.AuthorityInfo.UNDEFINED, + /*isAppStandbyExempted=*/ isForegroundSyncRequest); } } finally { restoreCallingIdentity(identityToken); @@ -548,6 +552,12 @@ public final class ContentService extends IContentService.Stub { public void syncAsUser(SyncRequest request, int userId) { enforceCrossUserPermission(userId, "no permission to request sync as user: " + userId); int callerUid = Binder.getCallingUid(); + + final Bundle extras = request.getBundle(); + + validateExtras(callerUid, extras); + final boolean isForegroundSyncRequest = isForegroundSyncRequest(callerUid, extras); + // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. long identityToken = clearCallingIdentity(); @@ -556,8 +566,6 @@ public final class ContentService extends IContentService.Stub { if (syncManager == null) { return; } - - Bundle extras = request.getBundle(); long flextime = request.getSyncFlexTime(); long runAtTime = request.getSyncRunTime(); if (request.isPeriodic()) { @@ -575,7 +583,8 @@ public final class ContentService extends IContentService.Stub { } else { syncManager.scheduleSync( request.getAccount(), userId, callerUid, request.getProvider(), extras, - SyncStorageEngine.AuthorityInfo.UNDEFINED); + SyncStorageEngine.AuthorityInfo.UNDEFINED, + /*isAppStandbyExempted=*/ isForegroundSyncRequest); } } finally { restoreCallingIdentity(identityToken); @@ -649,10 +658,13 @@ public final class ContentService extends IContentService.Stub { "no permission to write the sync settings"); } + Bundle extras = new Bundle(request.getBundle()); + validateExtras(callingUid, extras); + long identityToken = clearCallingIdentity(); try { SyncStorageEngine.EndPoint info; - Bundle extras = new Bundle(request.getBundle()); + Account account = request.getAccount(); String provider = request.getProvider(); info = new SyncStorageEngine.EndPoint(account, provider, userId); @@ -787,6 +799,8 @@ public final class ContentService extends IContentService.Stub { mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); + validateExtras(Binder.getCallingUid(), extras); + int userId = UserHandle.getCallingUserId(); pollFrequency = clampPeriod(pollFrequency); @@ -815,6 +829,8 @@ public final class ContentService extends IContentService.Stub { mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); + validateExtras(Binder.getCallingUid(), extras); + final int callingUid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); @@ -1239,6 +1255,56 @@ public final class ContentService extends IContentService.Stub { return SyncStorageEngine.AuthorityInfo.UNDEFINED; } + private void validateExtras(int callingUid, Bundle extras) { + if (extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC) + || extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC) + ) { + switch (callingUid) { + case Process.ROOT_UID: + case Process.SHELL_UID: + case Process.SYSTEM_UID: + break; // Okay + default: + throw new SecurityException("Invalid extras specified."); + } + } + } + + private boolean isForegroundSyncRequest(int callingUid, Bundle extras) { + final boolean isForegroundRequest; + if (extras.getBoolean(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC)) { + isForegroundRequest = true; + } else if (extras.getBoolean(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC)) { + isForegroundRequest = false; + } else { + isForegroundRequest = isUidInForeground(callingUid); + } + extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC); + extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC); + + return isForegroundRequest; + } + + private boolean isUidInForeground(int uid) { + // If the caller is ADB, we assume it's a background request by default, because + // that's also the default of requests from the requestsync command. + // The requestsync command will always set either SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC or + // SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC (for non-periodic sync requests), + // so it shouldn't matter in practice. + switch (uid) { + case Process.SHELL_UID: + case Process.ROOT_UID: + return false; + } + final ActivityManagerInternal ami = + LocalServices.getService(ActivityManagerInternal.class); + if (ami != null) { + return ami.getUidProcessState(uid) + <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + } + return false; + } + /** * Hide this class since it is not part of api, * but current unittest framework requires it to be public diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 422d0cbe5594..de17ec782be4 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -576,9 +576,10 @@ public class SyncManager { mSyncStorageEngine = SyncStorageEngine.getSingleton(); mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { @Override - public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras) { + public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras, + boolean isAppStandbyExempted) { scheduleSync(info.account, info.userId, reason, info.provider, extras, - AuthorityInfo.UNDEFINED); + AuthorityInfo.UNDEFINED, isAppStandbyExempted); } }); @@ -608,7 +609,8 @@ public class SyncManager { if (!removed) { scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_SERVICE_CHANGED, - type.authority, null, AuthorityInfo.UNDEFINED); + type.authority, null, AuthorityInfo.UNDEFINED, + /*isAppStandbyExempted=*/ false); } } }, mSyncHandler); @@ -656,7 +658,8 @@ public class SyncManager { if (mAccountManagerInternal.hasAccountAccess(account, uid)) { scheduleSync(account, UserHandle.getUserId(uid), SyncOperation.REASON_ACCOUNTS_UPDATED, - null, null, AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS); + null, null, AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS, + /*isAppStandbyExempted=*/ false); } }); @@ -881,17 +884,19 @@ public class SyncManager { * Use {@link AuthorityInfo#UNDEFINED} to sync all authorities. */ public void scheduleSync(Account requestedAccount, int userId, int reason, - String requestedAuthority, Bundle extras, int targetSyncState) { + String requestedAuthority, Bundle extras, int targetSyncState, + boolean isAppStandbyExempted) { scheduleSync(requestedAccount, userId, reason, requestedAuthority, extras, targetSyncState, - 0 /* min delay */, true /* checkIfAccountReady */); + 0 /* min delay */, true /* checkIfAccountReady */, isAppStandbyExempted); } /** * @param minDelayMillis The sync can't land before this delay expires. */ private void scheduleSync(Account requestedAccount, int userId, int reason, - String requestedAuthority, Bundle extras, int targetSyncState, - final long minDelayMillis, boolean checkIfAccountReady) { + String requestedAuthority, Bundle extras, int targetSyncState, + final long minDelayMillis, boolean checkIfAccountReady, + boolean isAppStandbyExempted) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); if (extras == null) { extras = new Bundle(); @@ -1009,7 +1014,8 @@ public class SyncManager { && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) { scheduleSync(account.account, userId, reason, authority, finalExtras, targetSyncState, minDelayMillis, - true /* checkIfAccountReady */); + true /* checkIfAccountReady */, + isAppStandbyExempted); } } )); @@ -1060,7 +1066,7 @@ public class SyncManager { sendOnUnsyncableAccount(mContext, syncAdapterInfo, account.userId, () -> scheduleSync(account.account, account.userId, reason, authority, finalExtras, targetSyncState, minDelayMillis, - false)); + false, isAppStandbyExempted)); } else { // Initialisation sync. Bundle newExtras = new Bundle(); @@ -1078,7 +1084,8 @@ public class SyncManager { postScheduleSyncMessage( new SyncOperation(account.account, account.userId, owningUid, owningPackage, reason, source, - authority, newExtras, allowParallelSyncs), + authority, newExtras, allowParallelSyncs, + isAppStandbyExempted), minDelayMillis ); } @@ -1095,7 +1102,7 @@ public class SyncManager { postScheduleSyncMessage( new SyncOperation(account.account, account.userId, owningUid, owningPackage, reason, source, - authority, extras, allowParallelSyncs), + authority, extras, allowParallelSyncs, isAppStandbyExempted), minDelayMillis ); } @@ -1208,11 +1215,13 @@ public class SyncManager { * Schedule sync based on local changes to a provider. We wait for at least LOCAL_SYNC_DELAY * ms to batch syncs. */ - public void scheduleLocalSync(Account account, int userId, int reason, String authority) { + public void scheduleLocalSync(Account account, int userId, int reason, String authority, + boolean isAppStandbyExempted) { final Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); scheduleSync(account, userId, reason, authority, extras, - AuthorityInfo.UNDEFINED, LOCAL_SYNC_DELAY, true /* checkIfAccountReady */); + AuthorityInfo.UNDEFINED, LOCAL_SYNC_DELAY, true /* checkIfAccountReady */, + isAppStandbyExempted); } public SyncAdapterType[] getSyncAdapterTypes(int userId) { @@ -1480,7 +1489,11 @@ public class SyncManager { } // Check if duplicate syncs are pending. If found, keep one with least expected run time. + + // If any of the duplicate ones has exemption, then we inherit it. if (!syncOperation.isPeriodic) { + boolean inheritAppStandbyExemption = false; + // Check currently running syncs for (ActiveSyncContext asc: mActiveSyncContexts) { if (asc.mSyncOperation.key.equals(syncOperation.key)) { @@ -1496,14 +1509,14 @@ public class SyncManager { long now = SystemClock.elapsedRealtime(); syncOperation.expectedRuntime = now + minDelay; List<SyncOperation> pending = getAllPendingSyncs(); - SyncOperation opWithLeastExpectedRuntime = syncOperation; + SyncOperation syncToRun = syncOperation; for (SyncOperation op : pending) { if (op.isPeriodic) { continue; } if (op.key.equals(syncOperation.key)) { - if (opWithLeastExpectedRuntime.expectedRuntime > op.expectedRuntime) { - opWithLeastExpectedRuntime = op; + if (syncToRun.expectedRuntime > op.expectedRuntime) { + syncToRun = op; } duplicatesCount++; } @@ -1511,26 +1524,54 @@ public class SyncManager { if (duplicatesCount > 1) { Slog.e(TAG, "FATAL ERROR! File a bug if you see this."); } + + if (syncOperation != syncToRun) { + // If there's a duplicate with an earlier run time that's not exempted, + // and if the current operation is exempted with no minDelay, + // cancel the duplicate one and keep the current one. + // + // This means the duplicate one has a negative expected run time, but it hasn't + // been executed possibly because of app-standby. + + if (syncOperation.isAppStandbyExempted + && (minDelay == 0) + && !syncToRun.isAppStandbyExempted) { + syncToRun = syncOperation; + } + } + + // Cancel all other duplicate syncs. for (SyncOperation op : pending) { if (op.isPeriodic) { continue; } if (op.key.equals(syncOperation.key)) { - if (op != opWithLeastExpectedRuntime) { + if (op != syncToRun) { if (isLoggable) { Slog.v(TAG, "Cancelling duplicate sync " + op); } + if (op.isAppStandbyExempted) { + inheritAppStandbyExemption = true; + } cancelJob(op, "scheduleSyncOperationH-duplicate"); } } } - if (opWithLeastExpectedRuntime != syncOperation) { + if (syncToRun != syncOperation) { // Don't schedule because a duplicate sync with earlier expected runtime exists. if (isLoggable) { Slog.v(TAG, "Not scheduling because a duplicate exists."); } + + // TODO Should we give the winning one SYNC_EXTRAS_APP_STANDBY_EXEMPTED + // if the current one has it? return; } + + // If any of the duplicates had exemption, we exempt the current one. + if (inheritAppStandbyExemption) { + syncOperation.isAppStandbyExempted = true; + } } // Syncs that are re-scheduled shouldn't get a new job id. @@ -1547,12 +1588,18 @@ public class SyncManager { final int networkType = syncOperation.isNotAllowedOnMetered() ? JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY; + // Note this logic means when an exempted sync fails, + // the back-off one will inherit it too, and will be exempted from app-standby. + final int jobFlags = syncOperation.isAppStandbyExempted + ? JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY : 0; + JobInfo.Builder b = new JobInfo.Builder(syncOperation.jobId, new ComponentName(mContext, SyncJobService.class)) .setExtras(syncOperation.toJobInfoExtras()) .setRequiredNetworkType(networkType) .setPersisted(true) - .setPriority(priority); + .setPriority(priority) + .setFlags(jobFlags); if (syncOperation.isPeriodic) { b.setPeriodic(syncOperation.periodMillis, syncOperation.flexMillis); @@ -1683,12 +1730,12 @@ public class SyncManager { EndPoint target = new EndPoint(null, null, userId); updateRunningAccounts(target); - // Schedule sync for any accounts under started user. + // Schedule sync for any accounts under started user, but only the NOT_INITIALIZED adapters. final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId, mContext.getOpPackageName()); for (Account account : accounts) { scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, - AuthorityInfo.NOT_INITIALIZED); + AuthorityInfo.NOT_INITIALIZED, /*isAppStandbyExempted=*/ false); } } @@ -3144,7 +3191,8 @@ public class SyncManager { if (syncTargets != null) { scheduleSync(syncTargets.account, syncTargets.userId, SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider, - null, AuthorityInfo.NOT_INITIALIZED); + null, AuthorityInfo.NOT_INITIALIZED, + /*isAppStandbyExempted=*/ false); } } @@ -3211,7 +3259,7 @@ public class SyncManager { syncAdapterInfo.componentName.getPackageName(), SyncOperation.REASON_PERIODIC, SyncStorageEngine.SOURCE_PERIODIC, extras, syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID, - pollFrequencyMillis, flexMillis); + pollFrequencyMillis, flexMillis, /*isAppStandbyExempted=*/ false); final int syncOpState = computeSyncOpState(op); switch (syncOpState) { @@ -3590,7 +3638,8 @@ public class SyncManager { syncOperation.owningUid, syncOperation.owningPackage, syncOperation.reason, syncOperation.syncSource, info.provider, new Bundle(), - syncOperation.allowParallelSyncs)); + syncOperation.allowParallelSyncs, + syncOperation.isAppStandbyExempted)); } } @@ -3808,6 +3857,10 @@ public class SyncManager { if (key.equals(ContentResolver.SYNC_EXTRAS_INITIALIZE)) { return true; } +// if (key.equals(ContentResolver.SYNC_EXTRAS_APP_STANDBY_EXEMPTED)) { +// return true; +// } + // No need to check virtual flags such as SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC. return false; } diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java index 7d2cc0035847..f6b481920faf 100644 --- a/services/core/java/com/android/server/content/SyncOperation.java +++ b/services/core/java/com/android/server/content/SyncOperation.java @@ -98,29 +98,33 @@ public class SyncOperation { /** jobId of the JobScheduler job corresponding to this sync */ public int jobId; + /** Whether this operation should be exempted from the app-standby throttling. */ + public boolean isAppStandbyExempted; + public SyncOperation(Account account, int userId, int owningUid, String owningPackage, int reason, int source, String provider, Bundle extras, - boolean allowParallelSyncs) { + boolean allowParallelSyncs, boolean isAppStandbyExempted) { this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage, - reason, source, extras, allowParallelSyncs); + reason, source, extras, allowParallelSyncs, isAppStandbyExempted); } private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, - int reason, int source, Bundle extras, boolean allowParallelSyncs) { + int reason, int source, Bundle extras, boolean allowParallelSyncs, + boolean isAppStandbyExempted) { this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false, - NO_JOB_ID, 0, 0); + NO_JOB_ID, 0, 0, isAppStandbyExempted); } public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) { this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource, new Bundle(op.extras), op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId, - periodMillis, flexMillis); + periodMillis, flexMillis, /*isAppStandbyExempted=*/ false); } public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs, boolean isPeriodic, int sourcePeriodicId, long periodMillis, - long flexMillis) { + long flexMillis, boolean isAppStandbyExempted) { this.target = info; this.owningUid = owningUid; this.owningPackage = owningPackage; @@ -134,6 +138,7 @@ public class SyncOperation { this.flexMillis = flexMillis; this.jobId = NO_JOB_ID; this.key = toKey(); + this.isAppStandbyExempted = isAppStandbyExempted; } /* Get a one off sync operation instance from a periodic sync. */ @@ -143,7 +148,7 @@ public class SyncOperation { } SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource, new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */, - periodMillis, flexMillis); + periodMillis, flexMillis, /*isAppStandbyExempted=*/ false); return op; } @@ -161,6 +166,7 @@ public class SyncOperation { periodMillis = other.periodMillis; flexMillis = other.flexMillis; this.key = other.key; + isAppStandbyExempted = other.isAppStandbyExempted; } /** @@ -229,6 +235,7 @@ public class SyncOperation { jobInfoExtras.putLong("flexMillis", flexMillis); jobInfoExtras.putLong("expectedRuntime", expectedRuntime); jobInfoExtras.putInt("retries", retries); + jobInfoExtras.putBoolean("isAppStandbyExempted", isAppStandbyExempted); return jobInfoExtras; } @@ -249,6 +256,7 @@ public class SyncOperation { Bundle extras; boolean allowParallelSyncs, isPeriodic; long periodMillis, flexMillis; + boolean isAppStandbyExempted; if (!jobExtras.getBoolean("SyncManagerJob", false)) { return null; @@ -267,6 +275,7 @@ public class SyncOperation { initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID); periodMillis = jobExtras.getLong("periodMillis"); flexMillis = jobExtras.getLong("flexMillis"); + isAppStandbyExempted = jobExtras.getBoolean("isAppStandbyExempted", false); extras = new Bundle(); PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras"); @@ -288,7 +297,8 @@ public class SyncOperation { SyncStorageEngine.EndPoint target = new SyncStorageEngine.EndPoint(account, provider, userId); SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source, - extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis); + extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis, + isAppStandbyExempted); op.jobId = jobExtras.getInt("jobId"); op.expectedRuntime = jobExtras.getLong("expectedRuntime"); op.retries = jobExtras.getInt("retries"); @@ -375,6 +385,9 @@ public class SyncOperation { if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { sb.append(" EXPEDITED"); } + if (isAppStandbyExempted) { + sb.append(" STANDBY-EXEMPTED"); + } sb.append(" Reason="); sb.append(reasonToString(pm, reason)); if (isPeriodic) { diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index 5a37ee235219..8b67b7a27e7e 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -340,7 +340,8 @@ public class SyncStorageEngine { interface OnSyncRequestListener { /** Called when a sync is needed on an account(s) due to some change in state. */ - public void onSyncRequest(EndPoint info, int reason, Bundle extras); + public void onSyncRequest(EndPoint info, int reason, Bundle extras, + boolean exemptFromAppStandby); } interface PeriodicSyncAddedListener { @@ -675,7 +676,8 @@ public class SyncStorageEngine { if (sync) { requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName, - new Bundle()); + new Bundle(), + /* exemptFromAppStandby=*/ false); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); queueBackup(); @@ -736,7 +738,8 @@ public class SyncStorageEngine { writeAccountInfoLocked(); } if (syncable == AuthorityInfo.SYNCABLE) { - requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle()); + requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle(), + /*exemptFromAppStandby=*/ false); // Or the caller FG state? } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } @@ -908,7 +911,8 @@ public class SyncStorageEngine { } if (flag) { requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null, - new Bundle()); + new Bundle(), + /*exemptFromAppStandby=*/ false); // Or the caller FG state? } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED); @@ -2138,10 +2142,12 @@ public class SyncStorageEngine { } } - private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras) { + private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras, + boolean exemptFromAppStandby) { if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID && mSyncRequestListener != null) { - mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras); + mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras, + exemptFromAppStandby); } else { SyncRequest.Builder req = new SyncRequest.Builder() @@ -2153,7 +2159,7 @@ public class SyncStorageEngine { } private void requestSync(Account account, int userId, int reason, String authority, - Bundle extras) { + Bundle extras, boolean exemptFromAppStandby) { // If this is happening in the system process, then call the syncrequest listener // to make a request back to the SyncManager directly. // If this is probably a test instance, then call back through the ContentResolver @@ -2162,8 +2168,7 @@ public class SyncStorageEngine { && mSyncRequestListener != null) { mSyncRequestListener.onSyncRequest( new EndPoint(account, authority, userId), - reason, - extras); + reason, extras, exemptFromAppStandby); } else { ContentResolver.requestSync(account, authority, extras); } diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java index deaa34ca9f51..7c3ea4ffe86a 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java @@ -25,7 +25,7 @@ import android.test.suitebuilder.annotation.SmallTest; /** * Test for SyncOperation. * - * bit FrameworksServicesTests:com.android.server.content.SyncOperationTest + * atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java */ @SmallTest public class SyncOperationTest extends AndroidTestCase { @@ -59,7 +59,8 @@ public class SyncOperationTest extends AndroidTestCase { SyncOperation.REASON_PERIODIC, "authority1", b1, - false); + false, + /*isAppStandbyExempted=*/ false); // Same as op1 but different time infos SyncOperation op2 = new SyncOperation(account1, 0, @@ -67,7 +68,8 @@ public class SyncOperationTest extends AndroidTestCase { SyncOperation.REASON_PERIODIC, "authority1", b1, - false); + false, + /*isAppStandbyExempted=*/ false); // Same as op1 but different authority SyncOperation op3 = new SyncOperation(account1, 0, @@ -75,7 +77,8 @@ public class SyncOperationTest extends AndroidTestCase { SyncOperation.REASON_PERIODIC, "authority2", b1, - false); + false, + /*isAppStandbyExempted=*/ false); // Same as op1 but different account SyncOperation op4 = new SyncOperation(account2, 0, @@ -83,7 +86,8 @@ public class SyncOperationTest extends AndroidTestCase { SyncOperation.REASON_PERIODIC, "authority1", b1, - false); + false, + /*isAppStandbyExempted=*/ false); // Same as op1 but different bundle SyncOperation op5 = new SyncOperation(account1, 0, @@ -91,7 +95,8 @@ public class SyncOperationTest extends AndroidTestCase { SyncOperation.REASON_PERIODIC, "authority1", b2, - false); + false, + /*isAppStandbyExempted=*/ false); assertEquals(op1.key, op2.key); assertNotSame(op1.key, op3.key); @@ -111,7 +116,8 @@ public class SyncOperationTest extends AndroidTestCase { SyncOperation.REASON_PERIODIC, "authority1", b1, - false); + false, + /*isAppStandbyExempted=*/ false); PersistableBundle pb = op1.toJobInfoExtras(); SyncOperation op2 = SyncOperation.maybeCreateFromJobExtras(pb); @@ -138,7 +144,8 @@ public class SyncOperationTest extends AndroidTestCase { "provider", 0); Bundle extras = new Bundle(); SyncOperation periodic = new SyncOperation(ep, 0, "package", 0, 0, extras, false, true, - SyncOperation.NO_JOB_ID, 60000, 10000); + SyncOperation.NO_JOB_ID, 60000, 10000, + /*isAppStandbyExempted=*/ false); SyncOperation oneoff = periodic.createOneTimeSyncOperation(); assertFalse("Conversion to oneoff sync failed.", oneoff.isPeriodic); assertEquals("Period not restored", periodic.periodMillis, oneoff.periodMillis); diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java index 85de1f1c3e86..7209c7971145 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java @@ -31,6 +31,7 @@ import android.test.mock.MockContext; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; import com.android.internal.os.AtomicFile; @@ -44,6 +45,7 @@ import java.io.FileOutputStream; * * TODO Broken. Fix it. b/62485315 */ +@Suppress public class SyncStorageEngineTest extends AndroidTestCase { protected Account account1; @@ -101,7 +103,8 @@ public class SyncStorageEngineTest extends AndroidTestCase { SyncOperation.REASON_PERIODIC, SyncStorageEngine.SOURCE_LOCAL, authority, - Bundle.EMPTY, true); + Bundle.EMPTY, true, + /*isAppStandbyExempted=*/ false); long historyId = engine.insertStartSyncEvent(op, time0); long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2; engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0); |