am 32551ae5: Merge "Fix infinite boot-loop bug in SM." into klp-dev

* commit '32551ae5197744425d3057dc5d449e7943d35ba2':
  Fix infinite boot-loop bug in SM.
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 49dfdb5..04b4027 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1914,12 +1914,6 @@
     public static void addPeriodicSync(Account account, String authority, Bundle extras,
             long pollFrequency) {
         validateSyncExtrasBundle(extras);
-        if (account == null) {
-            throw new IllegalArgumentException("account must not be null");
-        }
-        if (authority == null) {
-            throw new IllegalArgumentException("authority must not be null");
-        }
         if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false)
                 || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false)
                 || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false)
@@ -1949,12 +1943,6 @@
      */
     public static void removePeriodicSync(Account account, String authority, Bundle extras) {
         validateSyncExtrasBundle(extras);
-        if (account == null) {
-            throw new IllegalArgumentException("account must not be null");
-        }
-        if (authority == null) {
-            throw new IllegalArgumentException("authority must not be null");
-        }
         try {
             getContentService().removePeriodicSync(account, authority, extras);
         } catch (RemoteException e) {
@@ -1972,12 +1960,6 @@
      * @return a list of PeriodicSync objects. This list may be empty but will never be null.
      */
     public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) {
-        if (account == null) {
-            throw new IllegalArgumentException("account must not be null");
-        }
-        if (authority == null) {
-            throw new IllegalArgumentException("authority must not be null");
-        }
         try {
             return getContentService().getPeriodicSyncs(account, authority);
         } catch (RemoteException e) {
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index d4e0c2a..6ca283d 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -408,6 +408,9 @@
             if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
                 throw new IllegalArgumentException("Sync target has already been defined.");
             }
+            if (authority != null && authority.length() == 0) {
+                throw new IllegalArgumentException("Authority must be non-empty");
+            }
             mSyncTarget = SYNC_TARGET_ADAPTER;
             mAccount = account;
             mAuthority = authority;
diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java
index 885ec9f..cb35ef1 100644
--- a/services/java/com/android/server/content/ContentService.java
+++ b/services/java/com/android/server/content/ContentService.java
@@ -39,6 +39,7 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -342,13 +343,11 @@
      * and
      *   anonymous OR provider sync.
      * Depending on the request, we enqueue to suit in the SyncManager.
-     * @param request
+     * @param request The request object. Validation of this object is done by its builder.
      */
     @Override
     public void sync(SyncRequest request) {
         Bundle extras = request.getBundle();
-        ContentResolver.validateSyncExtrasBundle(extras);
-
         long flextime = request.getSyncFlexTime();
         long runAtTime = request.getSyncRunTime();
         int userId = UserHandle.getCallingUserId();
@@ -401,8 +400,11 @@
      */
     @Override
     public void cancelSync(Account account, String authority) {
-        int userId = UserHandle.getCallingUserId();
+        if (authority != null && authority.length() == 0) {
+            throw new IllegalArgumentException("Authority must be non-empty");
+        }
 
+        int userId = UserHandle.getCallingUserId();
         // 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.
         long identityToken = clearCallingIdentity();
@@ -439,8 +441,8 @@
     public boolean getSyncAutomatically(Account account, String providerName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -456,10 +458,13 @@
 
     @Override
     public void setSyncAutomatically(Account account, String providerName, boolean sync) {
+        if (TextUtils.isEmpty(providerName)) {
+            throw new IllegalArgumentException("Authority must be non-empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -472,16 +477,20 @@
         }
     }
 
-    /**
-     * Old API. Schedule periodic sync with default flex time.
-     */
+    /** Old API. Schedule periodic sync with default flex time. */
     @Override
     public void addPeriodicSync(Account account, String authority, Bundle extras,
             long pollFrequency) {
+        if (account == null) {
+            throw new IllegalArgumentException("Account must not be null");
+        }
+        if (TextUtils.isEmpty(authority)) {
+            throw new IllegalArgumentException("Authority must not be empty.");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         if (pollFrequency < 60) {
             Slog.w(TAG, "Requested poll frequency of " + pollFrequency
                     + " seconds being rounded up to 60 seconds.");
@@ -503,10 +512,16 @@
 
     @Override
     public void removePeriodicSync(Account account, String authority, Bundle extras) {
+        if (account == null) {
+            throw new IllegalArgumentException("Account must not be null");
+        }
+        if (TextUtils.isEmpty(authority)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             PeriodicSync syncToRemove = new PeriodicSync(account, authority, extras,
@@ -527,10 +542,16 @@
 
     @Override
     public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+        if (account == null) {
+            throw new IllegalArgumentException("Account must not be null");
+        }
+        if (TextUtils.isEmpty(providerName)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
@@ -560,10 +581,13 @@
 
     @Override
     public void setIsSyncable(Account account, String providerName, int syncable) {
+        if (TextUtils.isEmpty(providerName)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -580,8 +604,8 @@
     public boolean getMasterSyncAutomatically() {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -598,8 +622,8 @@
     public void setMasterSyncAutomatically(boolean flag) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -632,8 +656,8 @@
     public List<SyncInfo> getCurrentSyncs() {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId);
@@ -643,10 +667,13 @@
     }
 
     public SyncStatusInfo getSyncStatus(Account account, String authority) {
+        if (TextUtils.isEmpty(authority)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -663,8 +690,8 @@
     public boolean isSyncPending(Account account, String authority) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java
index 2ae7bc7..71d8d99 100644
--- a/services/java/com/android/server/content/SyncManager.java
+++ b/services/java/com/android/server/content/SyncManager.java
@@ -67,6 +67,7 @@
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.text.format.Time;
+import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
@@ -2009,8 +2010,11 @@
             for (Pair<AuthorityInfo, SyncStatusInfo> info : infos) {
                 final AuthorityInfo authorityInfo = info.first;
                 final SyncStatusInfo status = info.second;
-                // skip the sync if the account of this operation no longer
-                // exists
+                if (TextUtils.isEmpty(authorityInfo.authority)) {
+                    Log.e(TAG, "Got an empty provider string. Skipping: " + authorityInfo);
+                    continue;
+                }
+                // skip the sync if the account of this operation no longer exists
                 if (!containsAccountAndUser(
                         accounts, authorityInfo.account, authorityInfo.userId)) {
                     continue;
diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java
index d51c2d7..41ef229 100644
--- a/services/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/java/com/android/server/content/SyncStorageEngine.java
@@ -1357,12 +1357,12 @@
      *
      * @param account the account we want to check
      * @param authority the authority whose row should be selected
-     * @return the SyncStatusInfo for the authority
+     * @return the SyncStatusInfo for the authority or null if none found.
      */
     public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId,
             String authority) {
         if (account == null || authority == null) {
-          throw new IllegalArgumentException();
+          return null;
         }
         synchronized (mAuthorities) {
             final int N = mSyncStatus.size();
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
index e44652f..f870e4c 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
@@ -670,6 +670,61 @@
         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 {