Sync too many retries

- When an one-shot sync with app-standby exempt fails too many times in a row,
drop the "exempt from app-standby" flag.

- Also obtain some constants from global settings so we can change them
in CTS.

Bug: 72443754
Test: Manual test (CTS WIP)

Change-Id: Ibdbb348a7ff26a0be04b8f2c256e1f6ead39907d
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 021c22c..fef6495 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10505,6 +10505,16 @@
         public static final String BATTERY_STATS_CONSTANTS = "battery_stats_constants";
 
         /**
+         * SyncManager specific settings.
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * @see com.android.server.content.SyncManagerConstants
+         */
+        public static final String SYNC_MANAGER_CONSTANTS = "sync_manager_constants";
+
+        /**
          * Whether or not App Standby feature is enabled. This controls throttling of apps
          * based on usage patterns and predictions.
          * Type: int (0 for false, 1 for true)
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 5bb9abe..a0b6297 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -366,6 +366,7 @@
                     Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
                     Settings.Global.STORAGE_BENCHMARK_INTERVAL,
                     Settings.Global.STORAGE_SETTINGS_CLOBBER_THRESHOLD,
+                    Settings.Global.SYNC_MANAGER_CONSTANTS,
                     Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
                     Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
                     Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES,
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index c3f020a..95a0246 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -97,9 +97,7 @@
 
         @Override
         public void onBootPhase(int phase) {
-            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
-                mService.systemReady();
-            }
+            mService.onBootPhase(phase);
         }
 
 
@@ -300,8 +298,15 @@
                 localeFilter, null, null);
     }
 
-    void systemReady() {
-        getSyncManager();
+    void onBootPhase(int phase) {
+        switch (phase) {
+            case SystemService.PHASE_SYSTEM_SERVICES_READY:
+                getSyncManager();
+                break;
+        }
+        if (mSyncManager != null) {
+            mSyncManager.onBootPhase(phase);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index de17ec7..d87a1bb 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -89,6 +89,7 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
+import com.android.server.SystemService;
 import com.android.server.job.JobSchedulerInternal;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
@@ -102,7 +103,6 @@
 import com.android.server.content.SyncStorageEngine.AuthorityInfo;
 import com.android.server.content.SyncStorageEngine.EndPoint;
 import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
-import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -158,18 +158,6 @@
     }
 
     /**
-     * When retrying a sync for the first time use this delay. After that
-     * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
-     * In milliseconds.
-     */
-    private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
-
-    /**
-     * Default the max sync retry time to this value.
-     */
-    private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
-
-    /**
      * How long to wait before retrying a sync that failed due to one already being in progress.
      */
     private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
@@ -449,6 +437,7 @@
     };
 
     private final SyncHandler mSyncHandler;
+    private final SyncManagerConstants mConstants;
 
     private volatile boolean mBootCompleted = false;
     private volatile boolean mJobServiceReady = false;
@@ -616,6 +605,7 @@
         }, mSyncHandler);
 
         mRand = new Random(System.currentTimeMillis());
+        mConstants = new SyncManagerConstants(context);
 
         IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
         context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
@@ -756,6 +746,14 @@
         mSyncHandler.post(() -> mLogger.log("onStopUser: user=", userHandle));
     }
 
+    public void onBootPhase(int phase) {
+        // Note SyncManager only receives PHASE_SYSTEM_SERVICES_READY and after.
+        switch (phase) {
+            case SystemService.PHASE_SYSTEM_SERVICES_READY:
+                mConstants.start();
+                break;
+        }
+    }
 
     private void whiteListExistingSyncAdaptersIfNeeded() {
         if (!mSyncStorageEngine.shouldGrantSyncAdaptersAccountAccess()) {
@@ -903,7 +901,10 @@
         }
         if (isLoggable) {
             Log.d(TAG, "one-time sync for: " + requestedAccount + " " + extras.toString() + " "
-                    + requestedAuthority);
+                    + requestedAuthority
+                    + " reason=" + reason
+                    + " checkIfAccountReady=" + checkIfAccountReady
+                    + " isAppStandbyExempted=" + isAppStandbyExempted);
         }
 
         AccountAndUser[] accounts = null;
@@ -1368,18 +1369,18 @@
                 return;
             }
             // Subsequent delays are the double of the previous delay.
-            newDelayInMs = previousSettings.second * 2;
+            newDelayInMs =
+                    (long) (previousSettings.second * mConstants.getRetryTimeIncreaseFactor());
         }
         if (newDelayInMs <= 0) {
             // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS.
-            newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
-                    (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
+            final long initialRetryMs = mConstants.getInitialSyncRetryTimeInSeconds() * 1000;
+            newDelayInMs = jitterize(initialRetryMs, (long)(initialRetryMs * 1.1));
         }
 
         // Cap the delay.
-        long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(),
-                Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
-                DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
+        final long maxSyncRetryTimeInSeconds = mConstants.getMaxSyncRetryTimeInSeconds();
+
         if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
             newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
         }
@@ -1930,6 +1931,7 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll) {
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         dumpSyncState(ipw);
+        mConstants.dump(pw, "");
         dumpSyncAdapters(ipw);
 
         if (dumpAll) {
@@ -3573,7 +3575,13 @@
                         reschedulePeriodicSyncH(syncOperation);
                     }
                 } else {
-                    Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
+                    Log.w(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
+
+                    syncOperation.retries++;
+                    if (syncOperation.retries > mConstants.getMaxRetriesWithAppStandbyExemption()) {
+                        syncOperation.isAppStandbyExempted = false;
+                    }
+
                     // the operation failed so increase the backoff time
                     increaseBackoffSetting(syncOperation.target);
                     if (!syncOperation.isPeriodic) {
diff --git a/services/core/java/com/android/server/content/SyncManagerConstants.java b/services/core/java/com/android/server/content/SyncManagerConstants.java
new file mode 100644
index 0000000..2f35687
--- /dev/null
+++ b/services/core/java/com/android/server/content/SyncManagerConstants.java
@@ -0,0 +1,146 @@
+/*
+ * 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.content.Context;
+import android.database.ContentObserver;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+
+public class SyncManagerConstants extends ContentObserver {
+    private static final String TAG = "SyncManagerConfig";
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+
+    private static final String KEY_INITIAL_SYNC_RETRY_TIME_IN_SECONDS =
+            "initial_sync_retry_time_in_seconds";
+    private static final int DEF_INITIAL_SYNC_RETRY_TIME_IN_SECONDS = 30;
+    private int mInitialSyncRetryTimeInSeconds = DEF_INITIAL_SYNC_RETRY_TIME_IN_SECONDS;
+
+    private static final String KEY_RETRY_TIME_INCREASE_FACTOR =
+            "retry_time_increase_factor";
+    private static final float DEF_RETRY_TIME_INCREASE_FACTOR = 2.0f;
+    private float mRetryTimeIncreaseFactor = DEF_RETRY_TIME_INCREASE_FACTOR;
+
+    private static final String KEY_MAX_SYNC_RETRY_TIME_IN_SECONDS =
+            "max_sync_retry_time_in_seconds";
+    private static final int DEF_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60;
+    private int mMaxSyncRetryTimeInSeconds = DEF_MAX_SYNC_RETRY_TIME_IN_SECONDS;
+
+    private static final String KEY_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION =
+            "max_retries_with_app_standby_exemption";
+    private static final int DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION = 5;
+    private int mMaxRetriesWithAppStandbyExemption = DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION;
+
+    protected SyncManagerConstants(Context context) {
+        super(null);
+        mContext = context;
+        refresh();
+    }
+
+    public void start() {
+        mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+                Settings.Global.SYNC_MANAGER_CONSTANTS), false, this);
+        refresh();
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        refresh();
+    }
+
+    private void refresh() {
+        synchronized (mLock) {
+
+            String newValue = Settings.Global.getString(mContext.getContentResolver(),
+                    Global.SYNC_MANAGER_CONSTANTS);
+            final KeyValueListParser parser = new KeyValueListParser(',');
+            try {
+                parser.setString(newValue);
+            } catch (IllegalArgumentException e) {
+                Slog.wtf(TAG, "Bad constants: " + newValue);
+            }
+
+            mInitialSyncRetryTimeInSeconds = parser.getInt(
+                    KEY_INITIAL_SYNC_RETRY_TIME_IN_SECONDS,
+                    DEF_INITIAL_SYNC_RETRY_TIME_IN_SECONDS);
+
+            mMaxSyncRetryTimeInSeconds = parser.getInt(
+                    KEY_MAX_SYNC_RETRY_TIME_IN_SECONDS,
+                    DEF_MAX_SYNC_RETRY_TIME_IN_SECONDS);
+
+            mRetryTimeIncreaseFactor = parser.getFloat(
+                    KEY_RETRY_TIME_INCREASE_FACTOR,
+                    DEF_RETRY_TIME_INCREASE_FACTOR);
+
+            mMaxRetriesWithAppStandbyExemption = parser.getInt(
+                    KEY_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION,
+                    DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION);
+        }
+    }
+
+    public int getInitialSyncRetryTimeInSeconds() {
+        synchronized (mLock) {
+            return mInitialSyncRetryTimeInSeconds;
+        }
+    }
+
+    public float getRetryTimeIncreaseFactor() {
+        synchronized (mLock) {
+            return mRetryTimeIncreaseFactor;
+        }
+    }
+
+    public int getMaxSyncRetryTimeInSeconds() {
+        synchronized (mLock) {
+            return mMaxSyncRetryTimeInSeconds;
+        }
+    }
+
+    public int getMaxRetriesWithAppStandbyExemption() {
+        synchronized (mLock) {
+            return mMaxRetriesWithAppStandbyExemption;
+        }
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.print(prefix);
+            pw.println("SyncManager Config:");
+
+            pw.print(prefix);
+            pw.print("  mInitialSyncRetryTimeInSeconds=");
+            pw.println(mInitialSyncRetryTimeInSeconds);
+
+            pw.print(prefix);
+            pw.print("  mRetryTimeIncreaseFactor=");
+            pw.println(mRetryTimeIncreaseFactor);
+
+            pw.print(prefix);
+            pw.print("  mMaxSyncRetryTimeInSeconds=");
+            pw.println(mMaxSyncRetryTimeInSeconds);
+
+            pw.print(prefix);
+            pw.print("  mMaxRetriesWithAppStandbyExemption=");
+            pw.println(mMaxRetriesWithAppStandbyExemption);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index f6b4819..96bdaea 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -408,6 +408,10 @@
                 extrasToStringBuilder(extras, sb);
             }
         }
+        if (retries > 0) {
+            sb.append(" Retries=");
+            sb.append(retries);
+        }
         return sb.toString();
     }