summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMakoto Onuki <omakoto@google.com>2018-01-31 17:22:36 -0800
committerMakoto Onuki <omakoto@google.com>2018-02-02 16:40:25 -0800
commit61283ecc7faeabd0556f4509aca7185bc112ce14 (patch)
tree3df5ca61fab88c3b449140fe45fe0dfb08fcf978
parent2ef26bf2dfe203312d6c71a01426191060ff4e46 (diff)
Exempt sync requests by FG app from app-standby
Test: manual test with request sync, etc Bug: 72443754 Change-Id: Iecf2d3a8c54451324a02ca2762bda72aa219bd92
-rw-r--r--cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java29
-rw-r--r--core/java/android/content/ContentResolver.java28
-rw-r--r--services/core/java/com/android/server/content/ContentService.java80
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java103
-rw-r--r--services/core/java/com/android/server/content/SyncOperation.java29
-rw-r--r--services/core/java/com/android/server/content/SyncStorageEngine.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java5
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);