Exempt sync requests by FG app from app-standby

Test: manual test with request sync, etc
Bug: 72443754
Change-Id: Iecf2d3a8c54451324a02ca2762bda72aa219bd92
This commit is contained in:
Makoto Onuki
2018-01-31 17:22:36 -08:00
parent 2ef26bf2df
commit 61283ecc7f
8 changed files with 254 additions and 66 deletions

View File

@@ -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" +

View File

@@ -165,6 +165,26 @@ public abstract class ContentResolver {
/** {@hide} Flag to allow sync to occur on metered network. */
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
@@ -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 {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);