Merge "AppStandby exemption: sync requested by FG apps" into pi-dev

This commit is contained in:
TreeHugger Robot
2018-03-30 01:26:35 +00:00
committed by Android (Google) Code Review
14 changed files with 368 additions and 493 deletions

View File

@@ -29,23 +29,21 @@ public class RequestSync {
private String[] mArgs;
private int mNextArg;
private String mCurArgData;
private boolean mIsForegroundRequest;
private int mExemptionFlag = ContentResolver.SYNC_EXEMPTION_NONE;
enum Operation {
REQUEST_SYNC {
@Override
void invoke(RequestSync caller) {
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);
final int flag = caller.mExemptionFlag;
caller.mExtras.putInt(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG, flag);
if (flag == ContentResolver.SYNC_EXEMPTION_NONE) {
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");
+ " pass a -f or -F option (use -h for help).\n");
}
final SyncRequest request =
new SyncRequest.Builder()
@@ -213,7 +211,10 @@ public class RequestSync {
mExtras.putBoolean(key, Boolean.valueOf(value));
} else if (opt.equals("-f") || opt.equals("--foreground")) {
mIsForegroundRequest = true;
mExemptionFlag = ContentResolver.SYNC_EXEMPTION_ACTIVE;
} else if (opt.equals("-F") || opt.equals("--top")) {
mExemptionFlag = ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP;
} else {
System.err.println("Error: Unknown option: " + opt);
@@ -293,7 +294,9 @@ public class RequestSync {
" -a|--authority <AUTHORITY>\n" +
" App-standby related options\n" +
"\n" +
" -f|--foreground (Exempt a sync from app standby)\n" +
" -f|--foreground (cause WORKING_SET, FREQUENT sync adapters" +
" to run immediately)\n" +
" -F|--top (cause even RARE sync adapters to run immediately)\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

@@ -183,10 +183,13 @@ public final class UsageStatsManager {
public static final int REASON_SUB_USAGE_SLICE_PINNED = 0x0009;
/** @hide */
public static final int REASON_SUB_USAGE_SLICE_PINNED_PRIV = 0x000A;
/** @hide */
public static final int REASON_SUB_USAGE_EXEMPTED_SYNC_START = 0x000B;
/** @hide */
public static final int REASON_SUB_PREDICTED_RESTORED = 0x0001;
/** @hide */
@IntDef(flag = false, prefix = { "STANDBY_BUCKET_" }, value = {
STANDBY_BUCKET_EXEMPTED,
@@ -665,6 +668,9 @@ public final class UsageStatsManager {
case REASON_SUB_USAGE_SLICE_PINNED_PRIV:
sb.append("slpp");
break;
case REASON_SUB_USAGE_EXEMPTED_SYNC_START:
sb.append("es");
break;
}
break;
}

View File

@@ -243,4 +243,12 @@ public abstract class UsageStatsManagerInternal {
*/
public abstract void reportAppJobState(String packageName, @UserIdInt int userId,
int numDeferredJobs, long timeSinceLastJobRun);
/**
* Report a sync that was scheduled by an active app is about to be executed.
*
* @param packageName name of the package that owns the sync adapter.
* @param userId which user the app is associated with
*/
public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId);
}

View File

@@ -166,24 +166,13 @@ 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.
* {@hide} Integer extra containing a SyncExemption flag.
*
* 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";
public static final String SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG = "v_exemption";
/**
* Set by the SyncManager to request that the SyncAdapter initialize itself for
@@ -525,6 +514,38 @@ public abstract class ContentResolver {
*/
public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 1<<1;
/**
* No exception, throttled by app standby normally.
* @hide
*/
public static final int SYNC_EXEMPTION_NONE = 0;
/**
* When executing a sync with this exemption, we'll put the target app in the ACTIVE bucket
* for 10 minutes. This will allow the sync adapter to schedule/run further syncs and jobs.
*
* Note this will still *not* let RARE apps to run syncs, because they still won't get network
* connection.
* @hide
*/
public static final int SYNC_EXEMPTION_ACTIVE = 1;
/**
* In addition to {@link #SYNC_EXEMPTION_ACTIVE}, we put the sync adapter app in the
* temp whitelist for 10 minutes, so that even RARE apps can run syncs right away.
* @hide
*/
public static final int SYNC_EXEMPTION_ACTIVE_WITH_TEMP = 2;
/** @hide */
@IntDef(flag = false, prefix = { "SYNC_EXEMPTION_" }, value = {
SYNC_EXEMPTION_NONE,
SYNC_EXEMPTION_ACTIVE,
SYNC_EXEMPTION_ACTIVE_WITH_TEMP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SyncExemption {}
// Always log queries which take 500ms+; shorter queries are
// sampled accordingly.
private static final boolean ENABLE_CONTENT_SAMPLE = false;

View File

@@ -26,6 +26,7 @@ import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentResolver.SyncExemption;
import android.content.Context;
import android.content.IContentService;
import android.content.ISyncStatusObserver;
@@ -78,7 +79,7 @@ import java.util.List;
*/
public final class ContentService extends IContentService.Stub {
static final String TAG = "ContentService";
static final boolean DEBUG = false;
static final boolean DEBUG = true;
public static class Lifecycle extends SystemService {
private ContentService mService;
@@ -451,7 +452,7 @@ public final class ContentService extends IContentService.Stub {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,
uri.getAuthority(), /*isAppStandbyExempted=*/ isUidInForeground(uid));
uri.getAuthority(), getSyncExemptionForCaller(uid));
}
}
@@ -508,7 +509,7 @@ public final class ContentService extends IContentService.Stub {
int uId = Binder.getCallingUid();
validateExtras(uId, extras);
final boolean isForegroundSyncRequest = isForegroundSyncRequest(uId, extras);
final int syncExemption = getSyncExemptionAndCleanUpExtrasForCaller(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.
@@ -518,7 +519,7 @@ public final class ContentService extends IContentService.Stub {
if (syncManager != null) {
syncManager.scheduleSync(account, userId, uId, authority, extras,
SyncStorageEngine.AuthorityInfo.UNDEFINED,
/*isAppStandbyExempted=*/ isForegroundSyncRequest);
syncExemption);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -561,7 +562,7 @@ public final class ContentService extends IContentService.Stub {
final Bundle extras = request.getBundle();
validateExtras(callerUid, extras);
final boolean isForegroundSyncRequest = isForegroundSyncRequest(callerUid, extras);
final int syncExemption = getSyncExemptionAndCleanUpExtrasForCaller(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.
@@ -589,7 +590,7 @@ public final class ContentService extends IContentService.Stub {
syncManager.scheduleSync(
request.getAccount(), userId, callerUid, request.getProvider(), extras,
SyncStorageEngine.AuthorityInfo.UNDEFINED,
/*isAppStandbyExempted=*/ isForegroundSyncRequest);
syncExemption);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -777,13 +778,15 @@ public final class ContentService extends IContentService.Stub {
"no permission to write the sync settings");
enforceCrossUserPermission(userId,
"no permission to modify the sync settings for user " + userId);
final int callingUid = Binder.getCallingUid();
final int syncExemptionFlag = getSyncExemptionForCaller(callingUid);
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.getSyncStorageEngine().setSyncAutomatically(account, userId,
providerName, sync);
providerName, sync, syncExemptionFlag);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -964,11 +967,14 @@ public final class ContentService extends IContentService.Stub {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
final int callingUid = Binder.getCallingUid();
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId,
getSyncExemptionForCaller(callingUid));
}
} finally {
restoreCallingIdentity(identityToken);
@@ -1263,9 +1269,7 @@ public final class ContentService extends IContentService.Stub {
}
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)
) {
if (extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG)) {
switch (callingUid) {
case Process.ROOT_UID:
case Process.SHELL_UID:
@@ -1277,39 +1281,36 @@ public final class ContentService extends IContentService.Stub {
}
}
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;
@SyncExemption
private int getSyncExemptionForCaller(int callingUid) {
return getSyncExemptionAndCleanUpExtrasForCaller(callingUid, null);
}
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;
@SyncExemption
private int getSyncExemptionAndCleanUpExtrasForCaller(int callingUid, Bundle extras) {
if (extras != null) {
final int exemption =
extras.getInt(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG, -1);
// Need to remove the virtual extra.
extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG);
if (exemption != -1) {
return exemption;
}
}
final ActivityManagerInternal ami =
LocalServices.getService(ActivityManagerInternal.class);
if (ami != null) {
return ami.getUidProcessState(uid)
<= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
final int procState = (ami != null)
? ami.getUidProcessState(callingUid)
: ActivityManager.PROCESS_STATE_NONEXISTENT;
if (procState <= ActivityManager.PROCESS_STATE_TOP) {
return ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP;
}
return false;
if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
return ContentResolver.SYNC_EXEMPTION_ACTIVE;
}
return ContentResolver.SYNC_EXEMPTION_NONE;
}
/**

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.content;
import android.app.usage.UsageStatsManagerInternal;
import android.os.SystemClock;
import android.util.Pair;
import com.android.server.AppStateTracker;
import com.android.server.LocalServices;
import java.util.HashMap;
class SyncAdapterStateFetcher {
private final HashMap<Pair<Integer, String>, Integer> mBucketCache =
new HashMap<>();
public SyncAdapterStateFetcher() {
}
/**
* Return sync adapter state with a cache.
*/
public int getStandbyBucket(int userId, String packageName) {
final Pair<Integer, String> key = Pair.create(userId, packageName);
final Integer cached = mBucketCache.get(key);
if (cached != null) {
return cached;
}
final UsageStatsManagerInternal usmi =
LocalServices.getService(UsageStatsManagerInternal.class);
if (usmi == null) {
return -1; // Unknown.
}
final int value = usmi.getAppStandbyBucket(packageName, userId,
SystemClock.elapsedRealtime());
mBucketCache.put(key, value);
return value;
}
/**
* Return UID active state.
*/
public boolean isAppActive(int uid) {
final AppStateTracker ast =
LocalServices.getService(AppStateTracker.class);
if (ast == null) {
return false;
}
return ast.isUidActive(uid);
}
}

View File

@@ -29,9 +29,11 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentResolver.SyncExemption;
import android.content.Context;
import android.content.ISyncAdapter;
import android.content.ISyncAdapterUnsyncableAccountCallback;
@@ -70,6 +72,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -88,6 +91,8 @@ import android.util.Slog;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ArrayUtils;
import com.android.server.DeviceIdleController;
import com.android.server.DeviceIdleController.LocalService;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.job.JobSchedulerInternal;
@@ -550,10 +555,6 @@ public class SyncManager {
return mJobScheduler;
}
/**
* Should only be created after {@link ContentService#systemReady()} so that
* {@link PackageManager} is ready to query.
*/
public SyncManager(Context context, boolean factoryTest) {
// Initialize the SyncStorageEngine first, before registering observers
// and creating threads and so on; it may fail if the disk is full.
@@ -566,9 +567,9 @@ public class SyncManager {
mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
@Override
public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras,
boolean isAppStandbyExempted) {
@SyncExemption int syncExemptionFlag) {
scheduleSync(info.account, info.userId, reason, info.provider, extras,
AuthorityInfo.UNDEFINED, isAppStandbyExempted);
AuthorityInfo.UNDEFINED, syncExemptionFlag);
}
});
@@ -599,7 +600,7 @@ public class SyncManager {
scheduleSync(null, UserHandle.USER_ALL,
SyncOperation.REASON_SERVICE_CHANGED,
type.authority, null, AuthorityInfo.UNDEFINED,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
}
}
}, mSyncHandler);
@@ -649,7 +650,7 @@ public class SyncManager {
scheduleSync(account, UserHandle.getUserId(uid),
SyncOperation.REASON_ACCOUNTS_UPDATED,
null, null, AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
}
});
@@ -883,9 +884,9 @@ public class SyncManager {
*/
public void scheduleSync(Account requestedAccount, int userId, int reason,
String requestedAuthority, Bundle extras, int targetSyncState,
boolean isAppStandbyExempted) {
@SyncExemption int syncExemptionFlag) {
scheduleSync(requestedAccount, userId, reason, requestedAuthority, extras, targetSyncState,
0 /* min delay */, true /* checkIfAccountReady */, isAppStandbyExempted);
0 /* min delay */, true /* checkIfAccountReady */, syncExemptionFlag);
}
/**
@@ -894,7 +895,7 @@ public class SyncManager {
private void scheduleSync(Account requestedAccount, int userId, int reason,
String requestedAuthority, Bundle extras, int targetSyncState,
final long minDelayMillis, boolean checkIfAccountReady,
boolean isAppStandbyExempted) {
@SyncExemption int syncExemptionFlag) {
final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
if (extras == null) {
extras = new Bundle();
@@ -904,7 +905,7 @@ public class SyncManager {
+ requestedAuthority
+ " reason=" + reason
+ " checkIfAccountReady=" + checkIfAccountReady
+ " isAppStandbyExempted=" + isAppStandbyExempted);
+ " syncExemptionFlag=" + syncExemptionFlag);
}
AccountAndUser[] accounts = null;
@@ -1016,7 +1017,7 @@ public class SyncManager {
scheduleSync(account.account, userId, reason, authority,
finalExtras, targetSyncState, minDelayMillis,
true /* checkIfAccountReady */,
isAppStandbyExempted);
syncExemptionFlag);
}
}
));
@@ -1067,7 +1068,7 @@ public class SyncManager {
sendOnUnsyncableAccount(mContext, syncAdapterInfo, account.userId,
() -> scheduleSync(account.account, account.userId, reason,
authority, finalExtras, targetSyncState, minDelayMillis,
false, isAppStandbyExempted));
false, syncExemptionFlag));
} else {
// Initialisation sync.
Bundle newExtras = new Bundle();
@@ -1086,7 +1087,7 @@ public class SyncManager {
new SyncOperation(account.account, account.userId,
owningUid, owningPackage, reason, source,
authority, newExtras, allowParallelSyncs,
isAppStandbyExempted),
syncExemptionFlag),
minDelayMillis
);
}
@@ -1103,7 +1104,7 @@ public class SyncManager {
postScheduleSyncMessage(
new SyncOperation(account.account, account.userId,
owningUid, owningPackage, reason, source,
authority, extras, allowParallelSyncs, isAppStandbyExempted),
authority, extras, allowParallelSyncs, syncExemptionFlag),
minDelayMillis
);
}
@@ -1217,12 +1218,12 @@ public class SyncManager {
* ms to batch syncs.
*/
public void scheduleLocalSync(Account account, int userId, int reason, String authority,
boolean isAppStandbyExempted) {
@SyncExemption int syncExemptionFlag) {
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 */,
isAppStandbyExempted);
syncExemptionFlag);
}
public SyncAdapterType[] getSyncAdapterTypes(int userId) {
@@ -1493,7 +1494,7 @@ public class SyncManager {
// If any of the duplicate ones has exemption, then we inherit it.
if (!syncOperation.isPeriodic) {
boolean inheritAppStandbyExemption = false;
int inheritedSyncExemptionFlag = ContentResolver.SYNC_EXEMPTION_NONE;
// Check currently running syncs
for (ActiveSyncContext asc: mActiveSyncContexts) {
@@ -1534,10 +1535,11 @@ public class SyncManager {
// 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) {
if ((minDelay == 0)
&& (syncToRun.syncExemptionFlag < syncOperation.syncExemptionFlag)) {
syncToRun = syncOperation;
inheritedSyncExemptionFlag =
Math.max(inheritedSyncExemptionFlag, syncToRun.syncExemptionFlag);
}
}
@@ -1551,9 +1553,8 @@ public class SyncManager {
if (isLoggable) {
Slog.v(TAG, "Cancelling duplicate sync " + op);
}
if (op.isAppStandbyExempted) {
inheritAppStandbyExemption = true;
}
inheritedSyncExemptionFlag =
Math.max(inheritedSyncExemptionFlag, op.syncExemptionFlag);
cancelJob(op, "scheduleSyncOperationH-duplicate");
}
}
@@ -1570,8 +1571,9 @@ public class SyncManager {
}
// If any of the duplicates had exemption, we exempt the current one.
if (inheritAppStandbyExemption) {
syncOperation.isAppStandbyExempted = true;
//
if (inheritedSyncExemptionFlag > ContentResolver.SYNC_EXEMPTION_NONE) {
syncOperation.syncExemptionFlag = inheritedSyncExemptionFlag;
}
}
@@ -1591,7 +1593,7 @@ public class SyncManager {
// 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
final int jobFlags = syncOperation.isAppStandbyExempted()
? JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY : 0;
JobInfo.Builder b = new JobInfo.Builder(syncOperation.jobId,
@@ -1615,6 +1617,19 @@ public class SyncManager {
b.setRequiresCharging(true);
}
if (syncOperation.syncExemptionFlag
== ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP) {
DeviceIdleController.LocalService dic =
LocalServices.getService(DeviceIdleController.LocalService.class);
if (dic != null) {
dic.addPowerSaveTempWhitelistApp(Process.SYSTEM_UID,
syncOperation.owningPackage,
mConstants.getKeyExemptionTempWhitelistDurationInSeconds() * 1000,
UserHandle.getUserId(syncOperation.owningUid),
/* sync=*/ false, "sync by top app");
}
}
getJobScheduler().scheduleAsPackage(b.build(), syncOperation.owningPackage,
syncOperation.target.userId, syncOperation.wakeLockName());
}
@@ -1736,7 +1751,7 @@ public class SyncManager {
mContext.getOpPackageName());
for (Account account : accounts) {
scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null,
AuthorityInfo.NOT_INITIALIZED, /*isAppStandbyExempted=*/ false);
AuthorityInfo.NOT_INITIALIZED, ContentResolver.SYNC_EXEMPTION_NONE);
}
}
@@ -1930,7 +1945,10 @@ public class SyncManager {
protected void dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
dumpSyncState(ipw);
final SyncAdapterStateFetcher buckets = new SyncAdapterStateFetcher();
dumpSyncState(ipw, buckets);
mConstants.dump(pw, "");
dumpSyncAdapters(ipw);
@@ -1991,7 +2009,7 @@ public class SyncManager {
return ret;
}
protected void dumpPendingSyncs(PrintWriter pw) {
protected void dumpPendingSyncs(PrintWriter pw, SyncAdapterStateFetcher buckets) {
List<SyncOperation> pendingSyncs = getAllPendingSyncs();
pw.print("Pending Syncs: ");
@@ -2001,14 +2019,14 @@ public class SyncManager {
int count = 0;
for (SyncOperation op: pendingSyncs) {
if (!op.isPeriodic) {
pw.println(op.dump(null, false));
pw.println(op.dump(null, false, buckets));
count++;
}
}
pw.println();
}
protected void dumpPeriodicSyncs(PrintWriter pw) {
protected void dumpPeriodicSyncs(PrintWriter pw, SyncAdapterStateFetcher buckets) {
List<SyncOperation> pendingSyncs = getAllPendingSyncs();
pw.print("Periodic Syncs: ");
@@ -2018,7 +2036,7 @@ public class SyncManager {
int count = 0;
for (SyncOperation op: pendingSyncs) {
if (op.isPeriodic) {
pw.println(op.dump(null, false));
pw.println(op.dump(null, false, buckets));
count++;
}
}
@@ -2075,7 +2093,7 @@ public class SyncManager {
return true;
}
protected void dumpSyncState(PrintWriter pw) {
protected void dumpSyncState(PrintWriter pw, SyncAdapterStateFetcher buckets) {
final StringBuilder sb = new StringBuilder();
pw.print("Data connected: "); pw.println(mDataConnectionIsConnected);
@@ -2150,13 +2168,13 @@ public class SyncManager {
sb.setLength(0);
pw.print(formatDurationHMS(sb, durationInSeconds));
pw.print(" - ");
pw.print(activeSyncContext.mSyncOperation.dump(pm, false));
pw.print(activeSyncContext.mSyncOperation.dump(pm, false, buckets));
pw.println();
}
pw.println();
dumpPendingSyncs(pw);
dumpPeriodicSyncs(pw);
dumpPendingSyncs(pw, buckets);
dumpPeriodicSyncs(pw, buckets);
// Join the installed sync adapter with the accounts list and emit for everything.
pw.println("Sync Status");
@@ -3219,7 +3237,7 @@ public class SyncManager {
scheduleSync(syncTargets.account, syncTargets.userId,
SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider,
null, AuthorityInfo.NOT_INITIALIZED,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
}
}
@@ -3286,7 +3304,7 @@ public class SyncManager {
syncAdapterInfo.componentName.getPackageName(), SyncOperation.REASON_PERIODIC,
SyncStorageEngine.SOURCE_PERIODIC, extras,
syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID,
pollFrequencyMillis, flexMillis, /*isAppStandbyExempted=*/ false);
pollFrequencyMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
final int syncOpState = computeSyncOpState(op);
switch (syncOpState) {
@@ -3431,6 +3449,15 @@ public class SyncManager {
Slog.v(TAG, syncContext.toString());
}
}
if (op.isAppStandbyExempted()) {
final UsageStatsManagerInternal usmi = LocalServices.getService(
UsageStatsManagerInternal.class);
if (usmi != null) {
usmi.reportExemptedSyncStart(op.owningPackage,
UserHandle.getUserId(op.owningUid));
}
}
// Connect to the sync adapter.
int targetUid;
ComponentName targetComponent;
@@ -3604,7 +3631,7 @@ public class SyncManager {
syncOperation.retries++;
if (syncOperation.retries > mConstants.getMaxRetriesWithAppStandbyExemption()) {
syncOperation.isAppStandbyExempted = false;
syncOperation.syncExemptionFlag = ContentResolver.SYNC_EXEMPTION_NONE;
}
// the operation failed so increase the backoff time
@@ -3672,7 +3699,7 @@ public class SyncManager {
syncOperation.reason,
syncOperation.syncSource, info.provider, new Bundle(),
syncOperation.allowParallelSyncs,
syncOperation.isAppStandbyExempted));
syncOperation.syncExemptionFlag));
}
}

View File

@@ -52,6 +52,12 @@ public class SyncManagerConstants extends ContentObserver {
private static final int DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION = 5;
private int mMaxRetriesWithAppStandbyExemption = DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION;
private static final String KEY_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS =
"exemption_temp_whitelist_duration_in_seconds";
private static final int DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS = 10 * 60;
private int mKeyExemptionTempWhitelistDurationInSeconds
= DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS;
protected SyncManagerConstants(Context context) {
super(null);
mContext = context;
@@ -97,6 +103,11 @@ public class SyncManagerConstants extends ContentObserver {
mMaxRetriesWithAppStandbyExemption = parser.getInt(
KEY_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION,
DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION);
mKeyExemptionTempWhitelistDurationInSeconds = parser.getInt(
KEY_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS,
DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS);
}
}
@@ -124,6 +135,12 @@ public class SyncManagerConstants extends ContentObserver {
}
}
public int getKeyExemptionTempWhitelistDurationInSeconds() {
synchronized (mLock) {
return mKeyExemptionTempWhitelistDurationInSeconds;
}
}
public void dump(PrintWriter pw, String prefix) {
synchronized (mLock) {
pw.print(prefix);
@@ -144,6 +161,10 @@ public class SyncManagerConstants extends ContentObserver {
pw.print(prefix);
pw.print(" mMaxRetriesWithAppStandbyExemption=");
pw.println(mMaxRetriesWithAppStandbyExemption);
pw.print(prefix);
pw.print(" mKeyExemptionTempWhitelistDurationInSeconds=");
pw.println(mKeyExemptionTempWhitelistDurationInSeconds);
}
}
}

View File

@@ -19,6 +19,7 @@ package com.android.server.content;
import android.accounts.Account;
import android.app.job.JobInfo;
import android.content.ContentResolver;
import android.content.ContentResolver.SyncExemption;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -98,33 +99,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;
@SyncExemption
public int syncExemptionFlag;
public SyncOperation(Account account, int userId, int owningUid, String owningPackage,
int reason, int source, String provider, Bundle extras,
boolean allowParallelSyncs, boolean isAppStandbyExempted) {
boolean allowParallelSyncs, @SyncExemption int syncExemptionFlag) {
this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage,
reason, source, extras, allowParallelSyncs, isAppStandbyExempted);
reason, source, extras, allowParallelSyncs, syncExemptionFlag);
}
private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
int reason, int source, Bundle extras, boolean allowParallelSyncs,
boolean isAppStandbyExempted) {
@SyncExemption int syncExemptionFlag) {
this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false,
NO_JOB_ID, 0, 0, isAppStandbyExempted);
NO_JOB_ID, 0, 0, syncExemptionFlag);
}
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, /*isAppStandbyExempted=*/ false);
periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
}
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, boolean isAppStandbyExempted) {
long flexMillis, @SyncExemption int syncExemptionFlag) {
this.target = info;
this.owningUid = owningUid;
this.owningPackage = owningPackage;
@@ -138,7 +139,7 @@ public class SyncOperation {
this.flexMillis = flexMillis;
this.jobId = NO_JOB_ID;
this.key = toKey();
this.isAppStandbyExempted = isAppStandbyExempted;
this.syncExemptionFlag = syncExemptionFlag;
}
/* Get a one off sync operation instance from a periodic sync. */
@@ -148,7 +149,7 @@ public class SyncOperation {
}
SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource,
new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */,
periodMillis, flexMillis, /*isAppStandbyExempted=*/ false);
periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
return op;
}
@@ -166,7 +167,7 @@ public class SyncOperation {
periodMillis = other.periodMillis;
flexMillis = other.flexMillis;
this.key = other.key;
isAppStandbyExempted = other.isAppStandbyExempted;
syncExemptionFlag = other.syncExemptionFlag;
}
/**
@@ -235,7 +236,7 @@ public class SyncOperation {
jobInfoExtras.putLong("flexMillis", flexMillis);
jobInfoExtras.putLong("expectedRuntime", expectedRuntime);
jobInfoExtras.putInt("retries", retries);
jobInfoExtras.putBoolean("isAppStandbyExempted", isAppStandbyExempted);
jobInfoExtras.putInt("syncExemptionFlag", syncExemptionFlag);
return jobInfoExtras;
}
@@ -256,7 +257,7 @@ public class SyncOperation {
Bundle extras;
boolean allowParallelSyncs, isPeriodic;
long periodMillis, flexMillis;
boolean isAppStandbyExempted;
int syncExemptionFlag;
if (!jobExtras.getBoolean("SyncManagerJob", false)) {
return null;
@@ -275,7 +276,8 @@ public class SyncOperation {
initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID);
periodMillis = jobExtras.getLong("periodMillis");
flexMillis = jobExtras.getLong("flexMillis");
isAppStandbyExempted = jobExtras.getBoolean("isAppStandbyExempted", false);
syncExemptionFlag = jobExtras.getInt("syncExemptionFlag",
ContentResolver.SYNC_EXEMPTION_NONE);
extras = new Bundle();
PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras");
@@ -298,7 +300,7 @@ public class SyncOperation {
new SyncStorageEngine.EndPoint(account, provider, userId);
SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source,
extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis,
isAppStandbyExempted);
syncExemptionFlag);
op.jobId = jobExtras.getInt("jobId");
op.expectedRuntime = jobExtras.getLong("expectedRuntime");
op.retries = jobExtras.getInt("retries");
@@ -361,10 +363,10 @@ public class SyncOperation {
@Override
public String toString() {
return dump(null, true);
return dump(null, true, null);
}
String dump(PackageManager pm, boolean shorter) {
String dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates) {
StringBuilder sb = new StringBuilder();
sb.append("JobId=").append(jobId)
.append(" ")
@@ -385,8 +387,18 @@ public class SyncOperation {
if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
sb.append(" EXPEDITED");
}
if (isAppStandbyExempted) {
sb.append(" STANDBY-EXEMPTED");
switch (syncExemptionFlag) {
case ContentResolver.SYNC_EXEMPTION_NONE:
break;
case ContentResolver.SYNC_EXEMPTION_ACTIVE:
sb.append(" STANDBY-EXEMPTED");
break;
case ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP:
sb.append(" STANDBY-EXEMPTED(TOP)");
break;
default:
sb.append(" ExemptionFlag=" + syncExemptionFlag);
break;
}
sb.append(" Reason=");
sb.append(reasonToString(pm, reason));
@@ -397,21 +409,31 @@ public class SyncOperation {
SyncManager.formatDurationHMS(sb, flexMillis);
sb.append(")");
}
if (retries > 0) {
sb.append(" Retries=");
sb.append(retries);
}
if (!shorter) {
sb.append(" Owner={");
UserHandle.formatUid(sb, owningUid);
sb.append(" ");
sb.append(owningPackage);
if (appStates != null) {
sb.append(" [");
sb.append(appStates.getStandbyBucket(
UserHandle.getUserId(owningUid), owningPackage));
sb.append("]");
if (appStates.isAppActive(owningUid)) {
sb.append(" [ACTIVE]");
}
}
sb.append("}");
if (!extras.keySet().isEmpty()) {
sb.append(" ");
extrasToStringBuilder(extras, sb);
}
}
if (retries > 0) {
sb.append(" Retries=");
sb.append(retries);
}
return sb.toString();
}
@@ -464,6 +486,10 @@ public class SyncOperation {
return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
}
boolean isAppStandbyExempted() {
return syncExemptionFlag != ContentResolver.SYNC_EXEMPTION_NONE;
}
static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
if (bundle == null) {
sb.append("null");

View File

@@ -22,6 +22,7 @@ import android.accounts.AccountManager;
import android.app.backup.BackupManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentResolver.SyncExemption;
import android.content.Context;
import android.content.ISyncStatusObserver;
import android.content.PeriodicSync;
@@ -341,7 +342,7 @@ public class SyncStorageEngine {
/** 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,
boolean exemptFromAppStandby);
@SyncExemption int syncExemptionFlag);
}
interface PeriodicSyncAddedListener {
@@ -647,7 +648,7 @@ public class SyncStorageEngine {
}
public void setSyncAutomatically(Account account, int userId, String providerName,
boolean sync) {
boolean sync, @SyncExemption int syncExemptionFlag) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
+ ", user " + userId + " -> " + sync);
@@ -677,7 +678,7 @@ public class SyncStorageEngine {
if (sync) {
requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName,
new Bundle(),
/* exemptFromAppStandby=*/ false);
syncExemptionFlag);
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
queueBackup();
@@ -739,7 +740,7 @@ public class SyncStorageEngine {
}
if (syncable == AuthorityInfo.SYNCABLE) {
requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle(),
/*exemptFromAppStandby=*/ false); // Or the caller FG state?
ContentResolver.SYNC_EXEMPTION_NONE);
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
@@ -900,7 +901,8 @@ public class SyncStorageEngine {
return true;
}
public void setMasterSyncAutomatically(boolean flag, int userId) {
public void setMasterSyncAutomatically(boolean flag, int userId,
@SyncExemption int syncExemptionFlag) {
synchronized (mAuthorities) {
Boolean auto = mMasterSyncAutomatically.get(userId);
if (auto != null && auto.equals(flag)) {
@@ -912,7 +914,7 @@ public class SyncStorageEngine {
if (flag) {
requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null,
new Bundle(),
/*exemptFromAppStandby=*/ false); // Or the caller FG state?
syncExemptionFlag);
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED);
@@ -2046,7 +2048,8 @@ public class SyncStorageEngine {
String value = c.getString(c.getColumnIndex("value"));
if (name == null) continue;
if (name.equals("listen_for_tickles")) {
setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0,
ContentResolver.SYNC_EXEMPTION_NONE);
} else if (name.startsWith("sync_provider_")) {
String provider = name.substring("sync_provider_".length(),
name.length());
@@ -2143,11 +2146,11 @@ public class SyncStorageEngine {
}
private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras,
boolean exemptFromAppStandby) {
@SyncExemption int syncExemptionFlag) {
if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
&& mSyncRequestListener != null) {
mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras,
exemptFromAppStandby);
syncExemptionFlag);
} else {
SyncRequest.Builder req =
new SyncRequest.Builder()
@@ -2159,7 +2162,7 @@ public class SyncStorageEngine {
}
private void requestSync(Account account, int userId, int reason, String authority,
Bundle extras, boolean exemptFromAppStandby) {
Bundle extras, @SyncExemption int syncExemptionFlag) {
// 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
@@ -2168,7 +2171,7 @@ public class SyncStorageEngine {
&& mSyncRequestListener != null) {
mSyncRequestListener.onSyncRequest(
new EndPoint(account, authority, userId),
reason, extras, exemptFromAppStandby);
reason, extras, syncExemptionFlag);
} else {
ContentResolver.requestSync(account, authority, extras);
}

View File

@@ -17,6 +17,7 @@
package com.android.server.content;
import android.accounts.Account;
import android.content.ContentResolver;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.test.AndroidTestCase;
@@ -60,7 +61,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority1",
b1,
false,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
// Same as op1 but different time infos
SyncOperation op2 = new SyncOperation(account1, 0,
@@ -69,7 +70,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority1",
b1,
false,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
// Same as op1 but different authority
SyncOperation op3 = new SyncOperation(account1, 0,
@@ -78,7 +79,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority2",
b1,
false,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
// Same as op1 but different account
SyncOperation op4 = new SyncOperation(account2, 0,
@@ -87,7 +88,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority1",
b1,
false,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
// Same as op1 but different bundle
SyncOperation op5 = new SyncOperation(account1, 0,
@@ -96,7 +97,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority1",
b2,
false,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
assertEquals(op1.key, op2.key);
assertNotSame(op1.key, op3.key);
@@ -117,7 +118,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority1",
b1,
false,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
PersistableBundle pb = op1.toJobInfoExtras();
SyncOperation op2 = SyncOperation.maybeCreateFromJobExtras(pb);
@@ -145,7 +146,7 @@ public class SyncOperationTest extends AndroidTestCase {
Bundle extras = new Bundle();
SyncOperation periodic = new SyncOperation(ep, 0, "package", 0, 0, extras, false, true,
SyncOperation.NO_JOB_ID, 60000, 10000,
/*isAppStandbyExempted=*/ false);
ContentResolver.SYNC_EXEMPTION_NONE);
SyncOperation oneoff = periodic.createOneTimeSyncOperation();
assertFalse("Conversion to oneoff sync failed.", oneoff.isPeriodic);
assertEquals("Period not restored", periodic.periodMillis, oneoff.periodMillis);

View File

@@ -1,349 +0,0 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.content;
import android.accounts.Account;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
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;
import java.io.File;
import java.io.FileOutputStream;
/**
* Test for SyncStorageEngine.
*
* bit FrameworksServicesTests:com.android.server.content.SyncStorageEngineTest
*
* TODO Broken. Fix it. b/62485315
*/
@Suppress
public class SyncStorageEngineTest extends AndroidTestCase {
protected Account account1;
protected Account account2;
protected ComponentName syncService1;
protected String authority1 = "testprovider";
protected Bundle defaultBundle;
protected final int DEFAULT_USER = 0;
/* Some default poll frequencies. */
final long dayPoll = (60 * 60 * 24);
final long dayFuzz = 60;
final long thousandSecs = 1000;
final long thousandSecsFuzz = 100;
MockContentResolver mockResolver;
SyncStorageEngine engine;
private File getSyncDir() {
return new File(new File(getContext().getFilesDir(), "system"), "sync");
}
@Override
public void setUp() {
account1 = new Account("a@example.com", "example.type");
account2 = new Account("b@example.com", "example.type");
syncService1 = new ComponentName("com.example", "SyncService");
// Default bundle.
defaultBundle = new Bundle();
defaultBundle.putInt("int_key", 0);
defaultBundle.putString("string_key", "hello");
// Set up storage engine.
mockResolver = new MockContentResolver();
engine = SyncStorageEngine.newTestInstance(
new TestContext(mockResolver, getContext()));
}
/**
* Test that we handle the case of a history row being old enough to purge before the
* corresponding sync is finished. This can happen if the clock changes while we are syncing.
*
*/
// TODO: this test causes AidlTest to fail. Omit for now
// @SmallTest
public void testPurgeActiveSync() throws Exception {
final Account account = new Account("a@example.com", "example.type");
final String authority = "testprovider";
MockContentResolver mockResolver = new MockContentResolver();
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
new TestContext(mockResolver, getContext()));
long time0 = 1000;
SyncOperation op = new SyncOperation(account, 0, 0, "foo",
SyncOperation.REASON_PERIODIC,
SyncStorageEngine.SOURCE_LOCAL,
authority,
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);
}
@LargeTest
public void testAuthorityPersistence() throws Exception {
final Account account1 = new Account("a@example.com", "example.type");
final Account account2 = new Account("b@example.com", "example.type.2");
final String authority1 = "testprovider1";
final String authority2 = "testprovider2";
engine.setMasterSyncAutomatically(false, 0);
engine.setIsSyncable(account1, 0, authority1, 1);
engine.setSyncAutomatically(account1, 0, authority1, true);
engine.setIsSyncable(account2, 0, authority1, 1);
engine.setSyncAutomatically(account2, 0, authority1, true);
engine.setIsSyncable(account1, 0, authority2, 1);
engine.setSyncAutomatically(account1, 0, authority2, false);
engine.setIsSyncable(account2, 0, authority2, 0);
engine.setSyncAutomatically(account2, 0, authority2, true);
engine.writeAllState();
engine.clearAndReadState();
assertEquals(true, engine.getSyncAutomatically(account1, 0, authority1));
assertEquals(true, engine.getSyncAutomatically(account2, 0, authority1));
assertEquals(false, engine.getSyncAutomatically(account1, 0, authority2));
assertEquals(true, engine.getSyncAutomatically(account2, 0, authority2));
assertEquals(1, engine.getIsSyncable(account1, 0, authority1));
assertEquals(1, engine.getIsSyncable(account2, 0, authority1));
assertEquals(1, engine.getIsSyncable(account1, 0, authority2));
assertEquals(0, engine.getIsSyncable(account2, 0, authority2));
}
@MediumTest
public void testListenForTicklesParsing() throws Exception {
byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<accounts>\n"
+ "<listenForTickles user=\"0\" enabled=\"false\" />"
+ "<listenForTickles user=\"1\" enabled=\"true\" />"
+ "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+ "<authority id=\"1\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+ "</accounts>\n").getBytes();
MockContentResolver mockResolver = new MockContentResolver();
final TestContext testContext = new TestContext(mockResolver, getContext());
File syncDir = getSyncDir();
syncDir.mkdirs();
AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
FileOutputStream fos = accountInfoFile.startWrite();
fos.write(accountsFileData);
accountInfoFile.finishWrite(fos);
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
assertEquals(false, engine.getMasterSyncAutomatically(0));
assertEquals(true, engine.getMasterSyncAutomatically(1));
assertEquals(true, engine.getMasterSyncAutomatically(2));
}
@MediumTest
public void testAuthorityRenaming() throws Exception {
final Account account1 = new Account("acc1", "type1");
final Account account2 = new Account("acc2", "type2");
final String authorityContacts = "contacts";
final String authorityCalendar = "calendar";
final String authorityOther = "other";
final String authorityContactsNew = "com.android.contacts";
final String authorityCalendarNew = "com.android.calendar";
MockContentResolver mockResolver = new MockContentResolver();
final TestContext testContext = new TestContext(mockResolver, getContext());
byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<accounts>\n"
+ "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n"
+ "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n"
+ "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n"
+ "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n"
+ "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n"
+ "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n"
+ "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+ " authority=\"com.android.calendar\" />\n"
+ "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+ " authority=\"com.android.contacts\" />\n"
+ "</accounts>\n").getBytes();
File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
syncDir.mkdirs();
AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
FileOutputStream fos = accountInfoFile.startWrite();
fos.write(accountsFileData);
accountInfoFile.finishWrite(fos);
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityContacts));
assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityCalendar));
assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityOther));
assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityContactsNew));
assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityCalendarNew));
assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContacts));
assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendar));
assertEquals(true, engine.getSyncAutomatically(account2, 0, authorityOther));
assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContactsNew));
assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendarNew));
}
@SmallTest
public void testSyncableMigration() throws Exception {
final Account account = new Account("acc", "type");
MockContentResolver mockResolver = new MockContentResolver();
final TestContext testContext = new TestContext(mockResolver, getContext());
byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<accounts>\n"
+ "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n"
+ "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n"
+ "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\""
+ " authority=\"other3\" />\n"
+ "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\""
+ " authority=\"other4\" />\n"
+ "</accounts>\n").getBytes();
File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
syncDir.mkdirs();
AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
FileOutputStream fos = accountInfoFile.startWrite();
fos.write(accountsFileData);
accountInfoFile.finishWrite(fos);
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
assertEquals(-1, engine.getIsSyncable(account, 0, "other1"));
assertEquals(1, engine.getIsSyncable(account, 0, "other2"));
assertEquals(0, engine.getIsSyncable(account, 0, "other3"));
assertEquals(1, engine.getIsSyncable(account, 0, "other4"));
}
/**
* Verify that the API cannot cause a run-time reboot by passing in the empty string as an
* authority. The problem here is that
* {@link SyncStorageEngine#getOrCreateAuthorityLocked(account, provider)} would register
* an empty authority which causes a RTE in {@link SyncManager#scheduleReadyPeriodicSyncs()}.
* This is not strictly a SSE test, but it does depend on the SSE data structures.
*/
@SmallTest
public void testExpectedIllegalArguments() throws Exception {
try {
ContentResolver.setSyncAutomatically(account1, "", true);
fail("empty provider string should throw IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
try {
ContentResolver.addPeriodicSync(account1, "", Bundle.EMPTY, 84000L);
fail("empty provider string should throw IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
try {
ContentResolver.removePeriodicSync(account1, "", Bundle.EMPTY);
fail("empty provider string should throw IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
try {
ContentResolver.cancelSync(account1, "");
fail("empty provider string should throw IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
try {
ContentResolver.setIsSyncable(account1, "", 0);
fail("empty provider string should throw IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
try {
ContentResolver.cancelSync(account1, "");
fail("empty provider string should throw IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
try {
ContentResolver.requestSync(account1, "", Bundle.EMPTY);
fail("empty provider string should throw IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
try {
ContentResolver.getSyncStatus(account1, "");
fail("empty provider string should throw IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
// Make sure we aren't blocking null account/provider for those functions that use it
// to specify ALL accounts/providers.
ContentResolver.requestSync(null, null, Bundle.EMPTY);
ContentResolver.cancelSync(null, null);
}
}
class TestContext extends ContextWrapper {
ContentResolver mResolver;
private final Context mRealContext;
public TestContext(ContentResolver resolver, Context realContext) {
super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
mRealContext = realContext;
mResolver = resolver;
}
@Override
public Resources getResources() {
return mRealContext.getResources();
}
@Override
public File getFilesDir() {
return mRealContext.getFilesDir();
}
@Override
public void enforceCallingOrSelfPermission(String permission, String message) {
}
@Override
public void sendBroadcast(Intent intent) {
}
@Override
public ContentResolver getContentResolver() {
return mResolver;
}
}

View File

@@ -24,6 +24,7 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_START;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_BACKGROUND;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN;
@@ -186,6 +187,7 @@ public class AppStandbyController {
static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
/** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */
static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11;
static final int MSG_REPORT_EXEMPTED_SYNC_START = 12;
long mCheckIdleIntervalMillis;
long mAppIdleParoleIntervalMillis;
@@ -202,6 +204,8 @@ public class AppStandbyController {
long mPredictionTimeoutMillis;
/** Maximum time a sync adapter associated with a CP should keep the buckets elevated. */
long mSyncAdapterTimeoutMillis;
/** Maximum time an exempted sync should keep the buckets elevated. */
long mExemptedSyncAdapterTimeoutMillis;
/** Maximum time a system interaction should keep the buckets elevated. */
long mSystemInteractionTimeoutMillis;
@@ -375,6 +379,21 @@ public class AppStandbyController {
}
}
void reportExemptedSyncStart(String packageName, int userId) {
if (!mAppIdleEnabled) return;
final long elapsedRealtime = mInjector.elapsedRealtime();
synchronized (mAppIdleLock) {
AppUsageHistory appUsage = mAppIdleHistory.reportUsage(packageName, userId,
STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_EXEMPTED_SYNC_START,
0,
elapsedRealtime + mExemptedSyncAdapterTimeoutMillis);
maybeInformListeners(packageName, userId, elapsedRealtime,
appUsage.currentBucket, appUsage.bucketingReason, false);
}
}
void setChargingState(boolean charging) {
synchronized (mAppIdleLock) {
if (mCharging != charging) {
@@ -1274,6 +1293,11 @@ public class AppStandbyController {
.sendToTarget();
}
void postReportExemptedSyncStart(String packageName, int userId) {
mHandler.obtainMessage(MSG_REPORT_EXEMPTED_SYNC_START, userId, 0, packageName)
.sendToTarget();
}
void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) {
synchronized (mAppIdleLock) {
mAppIdleHistory.dump(idpw, userId, pkg);
@@ -1488,6 +1512,11 @@ public class AppStandbyController {
checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2,
mInjector.elapsedRealtime());
break;
case MSG_REPORT_EXEMPTED_SYNC_START:
reportExemptedSyncStart((String) msg.obj, msg.arg1);
break;
default:
super.handleMessage(msg);
break;
@@ -1550,6 +1579,7 @@ public class AppStandbyController {
"system_update_usage_duration";
private static final String KEY_PREDICTION_TIMEOUT = "prediction_timeout";
private static final String KEY_SYNC_ADAPTER_HOLD_DURATION = "sync_adapter_duration";
private static final String KEY_EXEMPTED_SYNC_HOLD_DURATION = "exempted_sync_duration";
private static final String KEY_SYSTEM_INTERACTION_HOLD_DURATION =
"system_interaction_duration";
public static final long DEFAULT_STRONG_USAGE_TIMEOUT = 1 * ONE_HOUR;
@@ -1557,6 +1587,7 @@ public class AppStandbyController {
public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = 2 * ONE_HOUR;
public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT = 10 * ONE_MINUTE;
public static final long DEFAULT_SYNC_ADAPTER_TIMEOUT = 10 * ONE_MINUTE;
public static final long DEFAULT_EXEMPTED_SYNC_TIMEOUT = 10 * ONE_MINUTE;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -1632,6 +1663,9 @@ public class AppStandbyController {
mSyncAdapterTimeoutMillis = mParser.getDurationMillis
(KEY_SYNC_ADAPTER_HOLD_DURATION,
COMPRESS_TIME ? ONE_MINUTE : DEFAULT_SYNC_ADAPTER_TIMEOUT);
mExemptedSyncAdapterTimeoutMillis = mParser.getDurationMillis
(KEY_EXEMPTED_SYNC_HOLD_DURATION,
COMPRESS_TIME ? ONE_MINUTE : DEFAULT_EXEMPTED_SYNC_TIMEOUT);
mSystemInteractionTimeoutMillis = mParser.getDurationMillis
(KEY_SYSTEM_INTERACTION_HOLD_DURATION,
COMPRESS_TIME ? ONE_MINUTE : DEFAULT_SYSTEM_INTERACTION_TIMEOUT);

View File

@@ -1279,5 +1279,10 @@ public class UsageStatsService extends SystemService implements
public void onAdminDataAvailable() {
mAppStandby.onAdminDataAvailable();
}
@Override
public void reportExemptedSyncStart(String packageName, int userId) {
mAppStandby.postReportExemptedSyncStart(packageName, userId);
}
}
}