diff --git a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java index b76d6694ca2db..37b7acfaf5e6a 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 @@ 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 \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 d27265259daf7..eafe91a3450a3 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -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; } diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 09ced2648de1a..b8628a4d446bd 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -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); } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 440103a6d8a46..9f3df377c4e2f 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -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; diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 63308f894d09e..ec404feeceb7b 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.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; } /** 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 0000000000000..62fb751077557 --- /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, Integer> mBucketCache = + new HashMap<>(); + + public SyncAdapterStateFetcher() { + } + + /** + * Return sync adapter state with a cache. + */ + public int getStandbyBucket(int userId, String packageName) { + final Pair 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 70892685d8b49..d1f50b7338943 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.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 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 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)); } } diff --git a/services/core/java/com/android/server/content/SyncManagerConstants.java b/services/core/java/com/android/server/content/SyncManagerConstants.java index 061e4ca02d2d0..2a5858c3e1825 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 @@ 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); } } } diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java index 96bdaeabf17c2..d0975637e686f 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 @@ 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"); diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index 8b67b7a27e7ee..6081af8d6a55f 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.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); } 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 7c3ea4ffe86a0..e37ed7976a86d 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 @@ 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); 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 7209c79711452..0000000000000 --- 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 = ("\n" - + "\n" - + "" - + "" - + "\n" - + "\n" - + "\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 = ("\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\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 = ("\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\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 571ed00a28847..5f01518b74491 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_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); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 2258b243cc3a9..1fbc27b588545 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 class UsageStatsService extends SystemService implements public void onAdminDataAvailable() { mAppStandby.onAdminDataAvailable(); } + + @Override + public void reportExemptedSyncStart(String packageName, int userId) { + mAppStandby.postReportExemptedSyncStart(packageName, userId); + } } }