Merge "AppStandby exemption: sync requested by FG apps" into pi-dev
diff --git a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java
index b76d669..37b7acf 100644
--- a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java
+++ b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java
@@ -29,23 +29,21 @@
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 @@
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 @@
" -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" +
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index d272652..eafe91a 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -183,10 +183,13 @@
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 @@
case REASON_SUB_USAGE_SLICE_PINNED_PRIV:
sb.append("slpp");
break;
+ case REASON_SUB_USAGE_EXEMPTED_SYNC_START:
+ sb.append("es");
+ break;
}
break;
}
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 09ced26..b8628a4 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -243,4 +243,12 @@
*/
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);
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 440103a..9f3df37 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -166,24 +166,13 @@
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 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;
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 63308f8..ec404fe 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -26,6 +26,7 @@
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 @@
*/
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 @@
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 @@
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 @@
if (syncManager != null) {
syncManager.scheduleSync(account, userId, uId, authority, extras,
SyncStorageEngine.AuthorityInfo.UNDEFINED,
- /*isAppStandbyExempted=*/ isForegroundSyncRequest);
+ syncExemption);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -561,7 +562,7 @@
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 @@
syncManager.scheduleSync(
request.getAccount(), userId, callerUid, request.getProvider(), extras,
SyncStorageEngine.AuthorityInfo.UNDEFINED,
- /*isAppStandbyExempted=*/ isForegroundSyncRequest);
+ syncExemption);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -777,13 +778,15 @@
"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 @@
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 @@
}
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 @@
}
}
- 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;
}
/**
diff --git a/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java b/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java
new file mode 100644
index 0000000..62fb751
--- /dev/null
+++ b/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java
@@ -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);
+ }
+}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 7089268..d1f50b7 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -29,9 +29,11 @@
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.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 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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
+ requestedAuthority
+ " reason=" + reason
+ " checkIfAccountReady=" + checkIfAccountReady
- + " isAppStandbyExempted=" + isAppStandbyExempted);
+ + " syncExemptionFlag=" + syncExemptionFlag);
}
AccountAndUser[] accounts = null;
@@ -1016,7 +1017,7 @@
scheduleSync(account.account, userId, reason, authority,
finalExtras, targetSyncState, minDelayMillis,
true /* checkIfAccountReady */,
- isAppStandbyExempted);
+ syncExemptionFlag);
}
}
));
@@ -1067,7 +1068,7 @@
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 @@
new SyncOperation(account.account, account.userId,
owningUid, owningPackage, reason, source,
authority, newExtras, allowParallelSyncs,
- isAppStandbyExempted),
+ syncExemptionFlag),
minDelayMillis
);
}
@@ -1103,7 +1104,7 @@
postScheduleSyncMessage(
new SyncOperation(account.account, account.userId,
owningUid, owningPackage, reason, source,
- authority, extras, allowParallelSyncs, isAppStandbyExempted),
+ authority, extras, allowParallelSyncs, syncExemptionFlag),
minDelayMillis
);
}
@@ -1217,12 +1218,12 @@
* 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 @@
// 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 @@
// 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 @@
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 @@
}
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
syncOperation.reason,
syncOperation.syncSource, info.provider, new Bundle(),
syncOperation.allowParallelSyncs,
- syncOperation.isAppStandbyExempted));
+ syncOperation.syncExemptionFlag));
}
}
diff --git a/services/core/java/com/android/server/content/SyncManagerConstants.java b/services/core/java/com/android/server/content/SyncManagerConstants.java
index 061e4ca..2a5858c 100644
--- a/services/core/java/com/android/server/content/SyncManagerConstants.java
+++ b/services/core/java/com/android/server/content/SyncManagerConstants.java
@@ -52,6 +52,12 @@
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 @@
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 int getKeyExemptionTempWhitelistDurationInSeconds() {
+ synchronized (mLock) {
+ return mKeyExemptionTempWhitelistDurationInSeconds;
+ }
+ }
+
public void dump(PrintWriter pw, String prefix) {
synchronized (mLock) {
pw.print(prefix);
@@ -144,6 +161,10 @@
pw.print(prefix);
pw.print(" mMaxRetriesWithAppStandbyExemption=");
pw.println(mMaxRetriesWithAppStandbyExemption);
+
+ pw.print(prefix);
+ pw.print(" mKeyExemptionTempWhitelistDurationInSeconds=");
+ pw.println(mKeyExemptionTempWhitelistDurationInSeconds);
}
}
}
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 96bdaea..d097563 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -19,6 +19,7 @@
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 @@
/** 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 @@
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 @@
}
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 @@
periodMillis = other.periodMillis;
flexMillis = other.flexMillis;
this.key = other.key;
- isAppStandbyExempted = other.isAppStandbyExempted;
+ syncExemptionFlag = other.syncExemptionFlag;
}
/**
@@ -235,7 +236,7 @@
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 @@
Bundle extras;
boolean allowParallelSyncs, isPeriodic;
long periodMillis, flexMillis;
- boolean isAppStandbyExempted;
+ int syncExemptionFlag;
if (!jobExtras.getBoolean("SyncManagerJob", false)) {
return null;
@@ -275,7 +276,8 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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");
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 8b67b7a..6081af8 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -22,6 +22,7 @@
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 @@
/** 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 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
&& mSyncRequestListener != null) {
mSyncRequestListener.onSyncRequest(
new EndPoint(account, authority, userId),
- reason, extras, exemptFromAppStandby);
+ reason, extras, syncExemptionFlag);
} 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 7c3ea4f..e37ed79 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -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 @@
"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 @@
"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 @@
"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 @@
"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 @@
"authority1",
b2,
false,
- /*isAppStandbyExempted=*/ false);
+ ContentResolver.SYNC_EXEMPTION_NONE);
assertEquals(op1.key, op2.key);
assertNotSame(op1.key, op3.key);
@@ -117,7 +118,7 @@
"authority1",
b1,
false,
- /*isAppStandbyExempted=*/ false);
+ ContentResolver.SYNC_EXEMPTION_NONE);
PersistableBundle pb = op1.toJobInfoExtras();
SyncOperation op2 = SyncOperation.maybeCreateFromJobExtras(pb);
@@ -145,7 +146,7 @@
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);
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
deleted file mode 100644
index 7209c79..0000000
--- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 571ed00a..5f01518 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -24,6 +24,7 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
.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 @@
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 @@
"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 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 @@
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);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 2258b24..1fbc27b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1279,5 +1279,10 @@
public void onAdminDataAvailable() {
mAppStandby.onAdminDataAvailable();
}
+
+ @Override
+ public void reportExemptedSyncStart(String packageName, int userId) {
+ mAppStandby.postReportExemptedSyncStart(packageName, userId);
+ }
}
}