Merge "AccountManagerService send pkg uid when creating notification channel"
diff --git a/api/system-current.txt b/api/system-current.txt
index 60d7f2f..9328420 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11393,6 +11393,7 @@
     field public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23; // 0xffffffe9
     field public static final int INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -26; // 0xffffffe6
     field public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10; // 0xfffffff6
+    field public static final int INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE = -27; // 0xffffffe5
     field public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8; // 0xfffffff8
     field public static final int INSTALL_FAILED_TEST_ONLY = -15; // 0xfffffff1
     field public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7; // 0xfffffff9
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e89dc0b..b4e6bd5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -808,6 +808,11 @@
         public final void scheduleReceiver(Intent intent, ActivityInfo info,
                 CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
                 boolean sync, int sendingUser, int processState) {
+            // TODO: Debugging added for bug:36406078 . Remove when done
+            if (Log.isLoggable("36406078", Log.DEBUG)) {
+                Log.d(TAG, "scheduleReceiver");
+            }
+
             updateProcessState(processState, false);
             ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                     sync, false, mAppThread.asBinder(), sendingUser);
@@ -894,6 +899,11 @@
                 CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                 String buildSerial) {
 
+            // TODO: Debugging added for bug:36406078 . Remove when done
+            if (Log.isLoggable("36406078", Log.DEBUG)) {
+                Log.d(TAG, "bindApplication: " + processName);
+            }
+
             if (services != null) {
                 // Setup the service cache in the ServiceManager
                 ServiceManager.initServiceCache(services);
@@ -3229,6 +3239,10 @@
         if (receiver.getPendingResult() != null) {
             data.finish();
         }
+        // TODO: Debugging added for bug:36406078 . Remove when done
+        if (Log.isLoggable("36406078", Log.DEBUG)) {
+            Log.d(TAG, "handleReceiver done");
+        }
     }
 
     // Instantiate a BackupAgent and tell it that it's alive
@@ -5764,6 +5778,10 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
+        // TODO: Debugging added for bug:36406078 . Remove when done
+        if (Log.isLoggable("36406078", Log.DEBUG)) {
+            Log.d(TAG, "handleBindApplication done");
+        }
     }
 
     /*package*/ final void finishInstrumentation(int resultCode, Bundle results) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index bb35928..71db5d3 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1069,6 +1069,16 @@
     public static final int INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -26;
 
     /**
+     * Installation return code: this is passed to the
+     * {@link IPackageInstallObserver} if the new package attempts to downgrade the
+     * target sandbox version of the app.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE = -27;
+
+    /**
      * Installation parse return code: this is passed to the
      * {@link IPackageInstallObserver} if the parser was given a path that is
      * not a file, or does not end with the expected '.apk' extension.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 96e2626..25f9c30 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -16692,6 +16692,16 @@
                                         + " target SDK " + oldTargetSdk + " does.");
                         return;
                     }
+                    // Prevent apps from downgrading their targetSandbox.
+                    final int oldTargetSandbox = oldPackage.applicationInfo.targetSandboxVersion;
+                    final int newTargetSandbox = pkg.applicationInfo.targetSandboxVersion;
+                    if (oldTargetSandbox == 2 && newTargetSandbox != 2) {
+                        res.setError(PackageManager.INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
+                                "Package " + pkg.packageName + " new target sandbox "
+                                + newTargetSandbox + " is incompatible with the previous value of"
+                                + oldTargetSandbox + ".");
+                        return;
+                    }
 
                     // Prevent installing of child packages
                     if (oldPackage.parentPackage != null) {
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 308632f..374aee1 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -22,7 +22,6 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.nullable;
 import static org.mockito.Mockito.times;
@@ -32,7 +31,6 @@
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AccountManagerInternal;
-import android.accounts.AuthenticatorDescription;
 import android.accounts.CantAddAccountActivity;
 import android.accounts.IAccountManagerResponse;
 import android.app.AppOpsManager;
@@ -49,11 +47,9 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.RegisteredServicesCacheListener;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
-import android.content.pm.RegisteredServicesCache.ServiceInfo;
 import android.database.Cursor;
 import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
@@ -62,6 +58,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.test.AndroidTestCase;
@@ -78,16 +75,18 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
 
 
 /**
@@ -2616,6 +2615,142 @@
         }
     }
 
+    @SmallTest
+    public void testConcurrencyReadWrite() throws Exception {
+        // Test 2 threads calling getAccounts and 1 thread setAuthToken
+        unlockSystemUser();
+        String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+
+        Account a1 = new Account("account1",
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+        mAms.addAccountExplicitly(a1, "p1", null);
+        List<String> errors = Collections.synchronizedList(new ArrayList<>());
+        int readerCount = 2;
+        ExecutorService es = Executors.newFixedThreadPool(readerCount + 1);
+        AtomicLong readTotalTime = new AtomicLong(0);
+        AtomicLong writeTotalTime = new AtomicLong(0);
+        final CyclicBarrier cyclicBarrier = new CyclicBarrier(readerCount + 1);
+
+        final int loopSize = 20;
+        for (int t = 0; t < readerCount; t++) {
+            es.submit(() -> {
+                for (int i = 0; i < loopSize; i++) {
+                    String logPrefix = Thread.currentThread().getName() + " " + i;
+                    waitForCyclicBarrier(cyclicBarrier);
+                    cyclicBarrier.reset();
+                    SystemClock.sleep(1); // Ensure that writer wins
+                    Log.d(TAG, logPrefix + " getAccounts started");
+                    long ti = System.currentTimeMillis();
+                    try {
+                        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+                        if (accounts == null || accounts.length != 1
+                                || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
+                                accounts[0].type)) {
+                            String msg = logPrefix + ": Unexpected accounts: " + Arrays
+                                    .toString(accounts);
+                            Log.e(TAG, "    " + msg);
+                            errors.add(msg);
+                        }
+                        Log.d(TAG, logPrefix + " getAccounts done");
+                    } catch (Exception e) {
+                        String msg = logPrefix + ": getAccounts failed " + e;
+                        Log.e(TAG, msg, e);
+                        errors.add(msg);
+                    }
+                    ti = System.currentTimeMillis() - ti;
+                    readTotalTime.addAndGet(ti);
+                }
+            });
+        }
+
+        es.submit(() -> {
+            for (int i = 0; i < loopSize; i++) {
+                String logPrefix = Thread.currentThread().getName() + " " + i;
+                waitForCyclicBarrier(cyclicBarrier);
+                long ti = System.currentTimeMillis();
+                Log.d(TAG, logPrefix + " setAuthToken started");
+                try {
+                    mAms.setAuthToken(a1, "t1", "v" + i);
+                    Log.d(TAG, logPrefix + " setAuthToken done");
+                } catch (Exception e) {
+                    errors.add(logPrefix + ": setAuthToken failed: " + e);
+                }
+                ti = System.currentTimeMillis() - ti;
+                writeTotalTime.addAndGet(ti);
+            }
+        });
+        es.shutdown();
+        assertTrue("Time-out waiting for jobs to finish",
+                es.awaitTermination(10, TimeUnit.SECONDS));
+        es.shutdownNow();
+        assertTrue("Errors: " + errors, errors.isEmpty());
+        Log.i(TAG, "testConcurrencyReadWrite: readTotalTime=" + readTotalTime + " avg="
+                + (readTotalTime.doubleValue() / readerCount / loopSize));
+        Log.i(TAG, "testConcurrencyReadWrite: writeTotalTime=" + writeTotalTime + " avg="
+                + (writeTotalTime.doubleValue() / loopSize));
+    }
+
+    @SmallTest
+    public void testConcurrencyRead() throws Exception {
+        // Test 2 threads calling getAccounts
+        unlockSystemUser();
+        String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+
+        Account a1 = new Account("account1",
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+        mAms.addAccountExplicitly(a1, "p1", null);
+        List<String> errors = Collections.synchronizedList(new ArrayList<>());
+        int readerCount = 2;
+        ExecutorService es = Executors.newFixedThreadPool(readerCount + 1);
+        AtomicLong readTotalTime = new AtomicLong(0);
+
+        final int loopSize = 20;
+        for (int t = 0; t < readerCount; t++) {
+            es.submit(() -> {
+                for (int i = 0; i < loopSize; i++) {
+                    String logPrefix = Thread.currentThread().getName() + " " + i;
+                    Log.d(TAG, logPrefix + " getAccounts started");
+                    long ti = System.currentTimeMillis();
+                    try {
+                        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+                        if (accounts == null || accounts.length != 1
+                                || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
+                                accounts[0].type)) {
+                            String msg = logPrefix + ": Unexpected accounts: " + Arrays
+                                    .toString(accounts);
+                            Log.e(TAG, "    " + msg);
+                            errors.add(msg);
+                        }
+                        Log.d(TAG, logPrefix + " getAccounts done");
+                    } catch (Exception e) {
+                        String msg = logPrefix + ": getAccounts failed " + e;
+                        Log.e(TAG, msg, e);
+                        errors.add(msg);
+                    }
+                    ti = System.currentTimeMillis() - ti;
+                    readTotalTime.addAndGet(ti);
+                }
+            });
+        }
+        es.shutdown();
+        assertTrue("Time-out waiting for jobs to finish",
+                es.awaitTermination(10, TimeUnit.SECONDS));
+        es.shutdownNow();
+        assertTrue("Errors: " + errors, errors.isEmpty());
+        Log.i(TAG, "testConcurrencyRead: readTotalTime=" + readTotalTime + " avg="
+                + (readTotalTime.doubleValue() / readerCount / loopSize));
+    }
+
+    private void waitForCyclicBarrier(CyclicBarrier cyclicBarrier) {
+        try {
+            cyclicBarrier.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            throw new IllegalStateException("Should not throw " + e, e);
+        }
+    }
+
     private void waitForLatch(CountDownLatch latch) {
         try {
             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);