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