Merge "Move activity-related methods to ActivityRecord"
diff --git a/api/current.txt b/api/current.txt
index a16b24e..652f23f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -17074,6 +17074,7 @@
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13; // 0xd
+    field public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14; // 0xe
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7; // 0x7
     field public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; // 0x0
     field public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; // 0x1
diff --git a/api/system-current.txt b/api/system-current.txt
index d33ccfc..ed2616a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -193,6 +193,7 @@
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
     field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
+    field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
     field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
     field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
     field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index 0570c3a..8dcb865 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -664,7 +664,7 @@
             nanosleep(&spec, nullptr);
         }
         clock_gettime(CLOCK_REALTIME, &spec);
-        timestampNs = (spec.tv_sec) * 1000 + spec.tv_nsec;
+        timestampNs = int64_t(spec.tv_sec) * 1000 + spec.tv_nsec;
     } while (file_exists_locked(timestampNs));
     return timestampNs;
 }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1b31792..fbcc785 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1955,6 +1955,7 @@
      *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA LOGICAL_MULTI_CAMERA}</li>
      *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}</li>
      *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA SECURE_IMAGE_DATA}</li>
+     *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA SYSTEM_CAMERA}</li>
      * </ul></p>
      * <p>This key is available on all devices.</p>
      *
@@ -1973,6 +1974,7 @@
      * @see #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
      * @see #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME
      * @see #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA
+     * @see #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index a0170da..2fa6125 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -996,6 +996,14 @@
      */
     public static final int REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13;
 
+    /**
+     * <p>The camera device is only accessible by Android's system components and privileged
+     * applications. Processes need to have the android.permission.SYSTEM_CAMERA in
+     * addition to android.permission.CAMERA in order to connect to this camera device.</p>
+     * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+     */
+    public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14;
+
     //
     // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
     //
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 4f7c8c5..bc03e51 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -706,16 +706,11 @@
         synchronized (sLock) {
             for (int i = 0; i < sListeners.size(); i++) {
                 if (namespace.equals(sListeners.valueAt(i).first)) {
-                    final int j = i;
-                    sListeners.valueAt(i).second.execute(new Runnable() {
-                        @Override
-                        public void run() {
-                            Map<String, String> propertyMap = new HashMap(1);
-                            propertyMap.put(name, value);
-                            sListeners.keyAt(j)
-                                    .onPropertiesChanged(new Properties(namespace, propertyMap));
-                        }
-
+                    final OnPropertiesChangedListener listener = sListeners.keyAt(i);
+                    sListeners.valueAt(i).second.execute(() -> {
+                        Map<String, String> propertyMap = new ArrayMap<>(1);
+                        propertyMap.put(name, value);
+                        listener.onPropertiesChanged(new Properties(namespace, propertyMap));
                     });
                 }
             }
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 30c4e90..9108c33 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -460,6 +460,7 @@
                 return;
             }
             try {
+                Slog.w(LOG_TAG, "Request fail: " + error);
                 callback.onFailure();
             } catch (RemoteException e) {
                 Slog.d(LOG_TAG, "Error calling callback");
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index d899dde..3f44e48 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -48,6 +48,8 @@
  * you have no control over this shrinking -- if you set a capacity and then remove an
  * item, it may reduce the capacity to better match the current size.  In the future an
  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
+ *
+ * <p>This structure is <b>NOT</b> thread-safe.</p>
  */
 public final class ArrayMap<K, V> implements Map<K, V> {
     private static final boolean DEBUG = false;
@@ -103,15 +105,21 @@
     static Object[] mTwiceBaseCache;
     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
     static int mTwiceBaseCacheSize;
+    /**
+     * Separate locks for each cache since each can be accessed independently of the other without
+     * risk of a deadlock.
+     */
+    private static final Object sBaseCacheLock = new Object();
+    private static final Object sTwiceBaseCacheLock = new Object();
 
-    final boolean mIdentityHashCode;
+    private final boolean mIdentityHashCode;
     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API.
     int[] mHashes;
     @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API.
     Object[] mArray;
     @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
     int mSize;
-    MapCollections<K, V> mCollections;
+    private MapCollections<K, V> mCollections;
 
     private static int binarySearchHashes(int[] hashes, int N, int hash) {
         try {
@@ -209,31 +217,57 @@
             throw new UnsupportedOperationException("ArrayMap is immutable");
         }
         if (size == (BASE_SIZE*2)) {
-            synchronized (ArrayMap.class) {
+            synchronized (sTwiceBaseCacheLock) {
                 if (mTwiceBaseCache != null) {
                     final Object[] array = mTwiceBaseCache;
                     mArray = array;
-                    mTwiceBaseCache = (Object[])array[0];
-                    mHashes = (int[])array[1];
-                    array[0] = array[1] = null;
-                    mTwiceBaseCacheSize--;
-                    if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
-                            + " now have " + mTwiceBaseCacheSize + " entries");
-                    return;
+                    try {
+                        mTwiceBaseCache = (Object[]) array[0];
+                        mHashes = (int[]) array[1];
+                        if (mHashes != null) {
+                            array[0] = array[1] = null;
+                            mTwiceBaseCacheSize--;
+                            if (DEBUG) {
+                                Log.d(TAG, "Retrieving 2x cache " + mHashes
+                                        + " now have " + mTwiceBaseCacheSize + " entries");
+                            }
+                            return;
+                        }
+                    } catch (ClassCastException e) {
+                    }
+                    // Whoops!  Someone trampled the array (probably due to not protecting
+                    // their access with a lock).  Our cache is corrupt; report and give up.
+                    Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
+                            + " [1]=" + array[1]);
+                    mTwiceBaseCache = null;
+                    mTwiceBaseCacheSize = 0;
                 }
             }
         } else if (size == BASE_SIZE) {
-            synchronized (ArrayMap.class) {
+            synchronized (sBaseCacheLock) {
                 if (mBaseCache != null) {
                     final Object[] array = mBaseCache;
                     mArray = array;
-                    mBaseCache = (Object[])array[0];
-                    mHashes = (int[])array[1];
-                    array[0] = array[1] = null;
-                    mBaseCacheSize--;
-                    if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
-                            + " now have " + mBaseCacheSize + " entries");
-                    return;
+                    try {
+                        mBaseCache = (Object[]) array[0];
+                        mHashes = (int[]) array[1];
+                        if (mHashes != null) {
+                            array[0] = array[1] = null;
+                            mBaseCacheSize--;
+                            if (DEBUG) {
+                                Log.d(TAG, "Retrieving 1x cache " + mHashes
+                                        + " now have " + mBaseCacheSize + " entries");
+                            }
+                            return;
+                        }
+                    } catch (ClassCastException e) {
+                    }
+                    // Whoops!  Someone trampled the array (probably due to not protecting
+                    // their access with a lock).  Our cache is corrupt; report and give up.
+                    Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
+                            + " [1]=" + array[1]);
+                    mBaseCache = null;
+                    mBaseCacheSize = 0;
                 }
             }
         }
@@ -242,10 +276,14 @@
         mArray = new Object[size<<1];
     }
 
+    /**
+     * Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other
+     * words, don't pass mHashes or mArray in directly.
+     */
     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
     private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
         if (hashes.length == (BASE_SIZE*2)) {
-            synchronized (ArrayMap.class) {
+            synchronized (sTwiceBaseCacheLock) {
                 if (mTwiceBaseCacheSize < CACHE_SIZE) {
                     array[0] = mTwiceBaseCache;
                     array[1] = hashes;
@@ -259,7 +297,7 @@
                 }
             }
         } else if (hashes.length == BASE_SIZE) {
-            synchronized (ArrayMap.class) {
+            synchronized (sBaseCacheLock) {
                 if (mBaseCacheSize < CACHE_SIZE) {
                     array[0] = mBaseCache;
                     array[1] = hashes;
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index 3fa914f..44c5af2 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -23,6 +23,7 @@
 
 import java.lang.reflect.Array;
 import java.util.Collection;
+import java.util.ConcurrentModificationException;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
@@ -46,6 +47,8 @@
  * you have no control over this shrinking -- if you set a capacity and then remove an
  * item, it may reduce the capacity to better match the current size.  In the future an
  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
+ *
+ * <p>This structure is <b>NOT</b> thread-safe.</p>
  */
 public final class ArraySet<E> implements Collection<E>, Set<E> {
     private static final boolean DEBUG = false;
@@ -72,15 +75,30 @@
     static int sBaseCacheSize;
     static Object[] sTwiceBaseCache;
     static int sTwiceBaseCacheSize;
+    /**
+     * Separate locks for each cache since each can be accessed independently of the other without
+     * risk of a deadlock.
+     */
+    private static final Object sBaseCacheLock = new Object();
+    private static final Object sTwiceBaseCacheLock = new Object();
 
-    final boolean mIdentityHashCode;
+    private final boolean mIdentityHashCode;
     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public API.
     int[] mHashes;
     @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public API.
     Object[] mArray;
     @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
     int mSize;
-    MapCollections<E, E> mCollections;
+    private MapCollections<E, E> mCollections;
+
+    private int binarySearch(int[] hashes, int hash) {
+        try {
+            return ContainerHelpers.binarySearch(hashes, mSize, hash);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new ConcurrentModificationException();
+        }
+    }
+
 
     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
     private int indexOf(Object key, int hash) {
@@ -91,7 +109,7 @@
             return ~0;
         }
 
-        int index = ContainerHelpers.binarySearch(mHashes, N, hash);
+        int index = binarySearch(mHashes, hash);
 
         // If the hash code wasn't found, then we have no entry for this key.
         if (index < 0) {
@@ -130,7 +148,7 @@
             return ~0;
         }
 
-        int index = ContainerHelpers.binarySearch(mHashes, N, 0);
+        int index = binarySearch(mHashes, 0);
 
         // If the hash code wasn't found, then we have no entry for this key.
         if (index < 0) {
@@ -163,20 +181,22 @@
     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
     private void allocArrays(final int size) {
         if (size == (BASE_SIZE * 2)) {
-            synchronized (ArraySet.class) {
+            synchronized (sTwiceBaseCacheLock) {
                 if (sTwiceBaseCache != null) {
                     final Object[] array = sTwiceBaseCache;
                     try {
                         mArray = array;
                         sTwiceBaseCache = (Object[]) array[0];
                         mHashes = (int[]) array[1];
-                        array[0] = array[1] = null;
-                        sTwiceBaseCacheSize--;
-                        if (DEBUG) {
-                            Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
-                                    + sTwiceBaseCacheSize + " entries");
-                    }
-                    return;
+                        if (mHashes != null) {
+                            array[0] = array[1] = null;
+                            sTwiceBaseCacheSize--;
+                            if (DEBUG) {
+                                Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
+                                        + sTwiceBaseCacheSize + " entries");
+                            }
+                            return;
+                        }
                     } catch (ClassCastException e) {
                     }
                     // Whoops!  Someone trampled the array (probably due to not protecting
@@ -188,20 +208,22 @@
                 }
             }
         } else if (size == BASE_SIZE) {
-            synchronized (ArraySet.class) {
+            synchronized (sBaseCacheLock) {
                 if (sBaseCache != null) {
                     final Object[] array = sBaseCache;
                     try {
                         mArray = array;
                         sBaseCache = (Object[]) array[0];
                         mHashes = (int[]) array[1];
-                        array[0] = array[1] = null;
-                        sBaseCacheSize--;
-                        if (DEBUG) {
-                            Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + sBaseCacheSize
-                                    + " entries");
+                        if (mHashes != null) {
+                            array[0] = array[1] = null;
+                            sBaseCacheSize--;
+                            if (DEBUG) {
+                                Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have "
+                                        + sBaseCacheSize + " entries");
+                            }
+                            return;
                         }
-                        return;
                     } catch (ClassCastException e) {
                     }
                     // Whoops!  Someone trampled the array (probably due to not protecting
@@ -218,10 +240,14 @@
         mArray = new Object[size];
     }
 
+    /**
+     * Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other
+     * words, don't pass mHashes or mArray in directly.
+     */
     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
     private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
         if (hashes.length == (BASE_SIZE * 2)) {
-            synchronized (ArraySet.class) {
+            synchronized (sTwiceBaseCacheLock) {
                 if (sTwiceBaseCacheSize < CACHE_SIZE) {
                     array[0] = sTwiceBaseCache;
                     array[1] = hashes;
@@ -237,7 +263,7 @@
                 }
             }
         } else if (hashes.length == BASE_SIZE) {
-            synchronized (ArraySet.class) {
+            synchronized (sBaseCacheLock) {
                 if (sBaseCacheSize < CACHE_SIZE) {
                     array[0] = sBaseCache;
                     array[1] = hashes;
@@ -308,10 +334,16 @@
     @Override
     public void clear() {
         if (mSize != 0) {
-            freeArrays(mHashes, mArray, mSize);
+            final int[] ohashes = mHashes;
+            final Object[] oarray = mArray;
+            final int osize = mSize;
             mHashes = EmptyArray.INT;
             mArray = EmptyArray.OBJECT;
             mSize = 0;
+            freeArrays(ohashes, oarray, osize);
+        }
+        if (mSize != 0) {
+            throw new ConcurrentModificationException();
         }
     }
 
@@ -320,6 +352,7 @@
      * items.
      */
     public void ensureCapacity(int minimumCapacity) {
+        final int oSize = mSize;
         if (mHashes.length < minimumCapacity) {
             final int[] ohashes = mHashes;
             final Object[] oarray = mArray;
@@ -330,6 +363,9 @@
             }
             freeArrays(ohashes, oarray, mSize);
         }
+        if (mSize != oSize) {
+            throw new ConcurrentModificationException();
+        }
     }
 
     /**
@@ -400,11 +436,10 @@
      *
      * @param value the object to add.
      * @return {@code true} if this set is modified, {@code false} otherwise.
-     * @throws ClassCastException
-     *             when the class of the object is inappropriate for this set.
      */
     @Override
     public boolean add(E value) {
+        final int oSize = mSize;
         final int hash;
         int index;
         if (value == null) {
@@ -419,9 +454,9 @@
         }
 
         index = ~index;
-        if (mSize >= mHashes.length) {
-            final int n = mSize >= (BASE_SIZE * 2) ? (mSize + (mSize >> 1))
-                    : (mSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
+        if (oSize >= mHashes.length) {
+            final int n = oSize >= (BASE_SIZE * 2) ? (oSize + (oSize >> 1))
+                    : (oSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
 
             if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n);
 
@@ -429,21 +464,29 @@
             final Object[] oarray = mArray;
             allocArrays(n);
 
+            if (oSize != mSize) {
+                throw new ConcurrentModificationException();
+            }
+
             if (mHashes.length > 0) {
-                if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0");
+                if (DEBUG) Log.d(TAG, "add: copy 0-" + oSize + " to 0");
                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
             }
 
-            freeArrays(ohashes, oarray, mSize);
+            freeArrays(ohashes, oarray, oSize);
         }
 
-        if (index < mSize) {
+        if (index < oSize) {
             if (DEBUG) {
-                Log.d(TAG, "add: move " + index + "-" + (mSize - index) + " to " + (index + 1));
+                Log.d(TAG, "add: move " + index + "-" + (oSize - index) + " to " + (index + 1));
             }
-            System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
-            System.arraycopy(mArray, index, mArray, index + 1, mSize - index);
+            System.arraycopy(mHashes, index, mHashes, index + 1, oSize - index);
+            System.arraycopy(mArray, index, mArray, index + 1, oSize - index);
+        }
+
+        if (oSize != mSize || index >= mHashes.length) {
+            throw new ConcurrentModificationException();
         }
 
         mHashes[index] = hash;
@@ -458,6 +501,7 @@
      * @hide
      */
     public void append(E value) {
+        final int oSize = mSize;
         final int index = mSize;
         final int hash = value == null ? 0
                 : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
@@ -476,6 +520,11 @@
             add(value);
             return;
         }
+
+        if (oSize != mSize) {
+            throw new ConcurrentModificationException();
+        }
+
         mSize = index + 1;
         mHashes[index] = hash;
         mArray[index] = value;
@@ -492,6 +541,9 @@
             if (N > 0) {
                 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
                 System.arraycopy(array.mArray, 0, mArray, 0, N);
+                if (0 != mSize) {
+                    throw new ConcurrentModificationException();
+                }
                 mSize = N;
             }
         } else {
@@ -549,12 +601,14 @@
             // Check if exception should be thrown outside of the critical path.
             throw new ArrayIndexOutOfBoundsException(index);
         }
+        final int oSize = mSize;
         final Object old = mArray[index];
-        if (mSize <= 1) {
+        if (oSize <= 1) {
             // Now empty.
             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
             clear();
         } else {
+            final int nSize = oSize - 1;
             if (shouldShrink()) {
                 // Shrunk enough to reduce size of arrays.
                 final int n = getNewShrunkenSize();
@@ -565,31 +619,33 @@
                 final Object[] oarray = mArray;
                 allocArrays(n);
 
-                mSize--;
                 if (index > 0) {
                     if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
                     System.arraycopy(ohashes, 0, mHashes, 0, index);
                     System.arraycopy(oarray, 0, mArray, 0, index);
                 }
-                if (index < mSize) {
+                if (index < nSize) {
                     if (DEBUG) {
-                        Log.d(TAG, "remove: copy from " + (index + 1) + "-" + mSize
+                        Log.d(TAG, "remove: copy from " + (index + 1) + "-" + nSize
                                 + " to " + index);
                     }
-                    System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
-                    System.arraycopy(oarray, index + 1, mArray, index, mSize - index);
+                    System.arraycopy(ohashes, index + 1, mHashes, index, nSize - index);
+                    System.arraycopy(oarray, index + 1, mArray, index, nSize - index);
                 }
             } else {
-                mSize--;
-                if (index < mSize) {
+                if (index < nSize) {
                     if (DEBUG) {
-                        Log.d(TAG, "remove: move " + (index + 1) + "-" + mSize + " to " + index);
+                        Log.d(TAG, "remove: move " + (index + 1) + "-" + nSize + " to " + index);
                     }
-                    System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
-                    System.arraycopy(mArray, index + 1, mArray, index, mSize - index);
+                    System.arraycopy(mHashes, index + 1, mHashes, index, nSize - index);
+                    System.arraycopy(mArray, index + 1, mArray, index, nSize - index);
                 }
-                mArray[mSize] = null;
+                mArray[nSize] = null;
             }
+            if (oSize != mSize) {
+                throw new ConcurrentModificationException();
+            }
+            mSize = nSize;
         }
         return (E) old;
     }
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index a97c330..66f7504 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -309,7 +309,7 @@
         }
 
         public void onFailure() {
-            Log.e(LOG_TAG, "Request failed.", null);
+            Log.e(LOG_TAG, "Request failed at " + mName, null);
             mLatch.countDown();
         }
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 96bb87b..4c54539 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -339,6 +339,8 @@
     cppflags: ["-Wno-conversion-null"],
 
     srcs: [
+        "android/graphics/apex/android_region.cpp",
+
         "android_graphics_Canvas.cpp",
         "android_graphics_ColorSpace.cpp",
         "android_graphics_drawable_AnimatedVectorDrawable.cpp",
diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp
index 90ca3bb..1fb5fe3 100644
--- a/core/jni/android/graphics/Region.cpp
+++ b/core/jni/android/graphics/Region.cpp
@@ -360,8 +360,4 @@
                                 NELEM(gRegionIterMethods));
 }
 
-SkRegion* android_graphics_Region_getSkRegion(JNIEnv* env, jobject regionObj) {
-    return GetSkRegion(env, regionObj);
-}
-
 } // namespace android
diff --git a/core/jni/android/graphics/Region.h b/core/jni/android/graphics/Region.h
deleted file mode 100644
index 2e8e109..0000000
--- a/core/jni/android/graphics/Region.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-#ifndef _ANDROID_GRAPHICS_REGION_H_
-#define _ANDROID_GRAPHICS_REGION_H_
-
-#include "jni.h"
-#include "SkRegion.h"
-
-namespace android {
-
-/* Gets the underlying SkRegion from a Region object. */
-extern SkRegion* android_graphics_Region_getSkRegion(JNIEnv* env, jobject regionObj);
-
-} // namespace android
-
-#endif // _ANDROID_GRAPHICS_REGION_H_
diff --git a/core/jni/android/graphics/apex/android_region.cpp b/core/jni/android/graphics/apex/android_region.cpp
new file mode 100644
index 0000000..2030e7e
--- /dev/null
+++ b/core/jni/android/graphics/apex/android_region.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "android/graphics/region.h"
+
+#include "GraphicsJNI.h"
+
+#include <SkRegion.h>
+
+static inline SkRegion::Iterator* ARegionIter_to_SkRegionIter(ARegionIterator* iterator) {
+    return reinterpret_cast<SkRegion::Iterator*>(iterator);
+}
+
+static inline ARegionIterator* SkRegionIter_to_ARegionIter(SkRegion::Iterator* iterator) {
+    return reinterpret_cast<ARegionIterator*>(iterator);
+}
+
+ARegionIterator* ARegionIterator_acquireIterator(JNIEnv* env, jobject regionObj) {
+    SkRegion* region = GraphicsJNI::getNativeRegion(env, regionObj);
+    return (!region) ? nullptr : SkRegionIter_to_ARegionIter(new SkRegion::Iterator(*region));
+}
+
+void ARegionIterator_releaseIterator(ARegionIterator* iterator) {
+    delete ARegionIter_to_SkRegionIter(iterator);
+}
+
+bool ARegionIterator_isComplex(ARegionIterator* iterator) {
+    return ARegionIter_to_SkRegionIter(iterator)->rgn()->isComplex();
+}
+
+bool ARegionIterator_isDone(ARegionIterator* iterator) {
+    return ARegionIter_to_SkRegionIter(iterator)->done();
+}
+
+void ARegionIterator_next(ARegionIterator* iterator) {
+    ARegionIter_to_SkRegionIter(iterator)->next();
+}
+
+ARect ARegionIterator_getRect(ARegionIterator* iterator) {
+    const SkIRect& rect = ARegionIter_to_SkRegionIter(iterator)->rect();
+    return { rect.fLeft, rect.fTop, rect.fRight, rect.fBottom };
+}
+
+ARect ARegionIterator_getTotalBounds(ARegionIterator* iterator) {
+    const SkIRect& bounds = ARegionIter_to_SkRegionIter(iterator)->rgn()->getBounds();
+    return { bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom };
+}
diff --git a/core/jni/android/graphics/apex/include/android/graphics/region.h b/core/jni/android/graphics/apex/include/android/graphics/region.h
new file mode 100644
index 0000000..961067a
--- /dev/null
+++ b/core/jni/android/graphics/apex/include/android/graphics/region.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+#ifndef ANDROID_GRAPHICS_REGION_H
+#define ANDROID_GRAPHICS_REGION_H
+
+#include <android/rect.h>
+#include <sys/cdefs.h>
+#include <jni.h>
+
+__BEGIN_DECLS
+
+/**
+* Opaque handle for a native graphics region iterator.
+*/
+typedef struct ARegionIterator ARegionIterator;
+
+/**
+ * Returns a iterator for a Java android.graphics.Region
+ *
+ * @param env
+ * @param region
+ * @return ARegionIterator that must be closed and must not live longer than the life
+ *         of the jobject.  It returns nullptr if the region is not a valid object.
+ */
+ARegionIterator* ARegionIterator_acquireIterator(JNIEnv* env, jobject region);
+
+void ARegionIterator_releaseIterator(ARegionIterator* iterator);
+
+bool ARegionIterator_isComplex(ARegionIterator* iterator);
+
+bool ARegionIterator_isDone(ARegionIterator* iterator);
+
+void ARegionIterator_next(ARegionIterator* iterator);
+
+ARect ARegionIterator_getRect(ARegionIterator* iterator);
+
+ARect ARegionIterator_getTotalBounds(ARegionIterator* iterator);
+
+__END_DECLS
+
+#ifdef	__cplusplus
+namespace android {
+namespace graphics {
+    class RegionIterator {
+    public:
+        RegionIterator(JNIEnv* env, jobject region)
+                : mIterator(ARegionIterator_acquireIterator(env, region)) {}
+        ~RegionIterator() { ARegionIterator_releaseIterator(mIterator); }
+
+        bool isValid() const { return mIterator != nullptr; }
+        bool isComplex() { return ARegionIterator_isComplex(mIterator); }
+        bool isDone() { return ARegionIterator_isDone(mIterator); }
+        void next() { ARegionIterator_next(mIterator); }
+        ARect getRect() { return ARegionIterator_getRect(mIterator); }
+        ARect getTotalBounds() const { return ARegionIterator_getTotalBounds(mIterator); }
+    private:
+        ARegionIterator* mIterator;
+    };
+}; // namespace graphics
+}; // namespace android
+
+#endif // __cplusplus
+#endif // ANDROID_GRAPHICS_REGION_H
\ No newline at end of file
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 98680fa1e..64caf33 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -22,7 +22,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/threads.h>
 
-#include <android/graphics/Region.h>
+#include <android/graphics/region.h>
 #include <gui/SurfaceControl.h>
 #include <ui/Region.h>
 
@@ -128,10 +128,9 @@
     jobject regionObj = env->GetObjectField(obj,
             gInputWindowHandleClassInfo.touchableRegion);
     if (regionObj) {
-        SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
-        for (SkRegion::Iterator it(*region); !it.done(); it.next()) {
-            const SkIRect& rect = it.rect();
-            mInfo.addTouchableRegion(Rect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom));
+        for (graphics::RegionIterator it(env, regionObj); !it.isDone(); it.next()) {
+            ARect rect = it.getRect();
+            mInfo.addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom));
         }
         env->DeleteLocalRef(regionObj);
     }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 096bf69..67f52f4 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -20,9 +20,9 @@
 #include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 #include "android_hardware_input_InputWindowHandle.h"
-#include "android/graphics/Region.h"
 #include "core_jni_helpers.h"
 
+#include <android/graphics/region.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android-base/chrono_utils.h>
 #include <nativehelper/JNIHelp.h>
@@ -425,20 +425,19 @@
 static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jobject regionObj) {
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
-    if (!region) {
+    graphics::RegionIterator iterator(env, regionObj);
+    if (!iterator.isValid()) {
         doThrowIAE(env);
         return;
     }
 
-    const SkIRect& b(region->getBounds());
-    Region reg(Rect(b.fLeft, b.fTop, b.fRight, b.fBottom));
-    if (region->isComplex()) {
-        SkRegion::Iterator it(*region);
-        while (!it.done()) {
-            const SkIRect& r(it.rect());
-            reg.addRectUnchecked(r.fLeft, r.fTop, r.fRight, r.fBottom);
-            it.next();
+    ARect bounds = iterator.getTotalBounds();
+    Region reg({bounds.left, bounds.top, bounds.right, bounds.bottom});
+    if (iterator.isComplex()) {
+        while (!iterator.isDone()) {
+            ARect rect = iterator.getRect();
+            reg.addRectUnchecked(rect.left, rect.top, rect.right, rect.bottom);
+            iterator.next();
         }
     }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c0a8650..9738759 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1264,6 +1264,15 @@
         android:description="@string/permdesc_camera"
         android:protectionLevel="dangerous|instant" />
 
+      <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
+           system only camera devices.
+           <p>Protection level: system|signature
+           @hide -->
+    <permission android:name="android.permission.SYSTEM_CAMERA"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_systemCamera"
+        android:description="@string/permdesc_systemCamera"
+        android:protectionLevel="system|signature" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device sensors                           -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cdfa0439..2b97bf8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1173,6 +1173,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_camera">This app can take pictures and record videos using the camera at any time.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permlab_systemCamera">Allow an application or service access to system cameras to take pictures and videos</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permdesc_systemCamera">This privileged | system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_vibrate">control vibration</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/tests/coretests/src/android/util/ArraySetTest.java b/core/tests/coretests/src/android/util/ArraySetTest.java
new file mode 100644
index 0000000..f1bebfb
--- /dev/null
+++ b/core/tests/coretests/src/android/util/ArraySetTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 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 android.util;
+
+import androidx.test.filters.LargeTest;
+
+import junit.framework.TestCase;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.ConcurrentModificationException;
+
+/**
+ * Unit tests for ArraySet that don't belong in CTS.
+ */
+@LargeTest
+public class ArraySetTest extends TestCase {
+    private static final String TAG = "ArraySetTest";
+    ArraySet<String> mSet = new ArraySet<>();
+
+    @After
+    public void tearDown() {
+        mSet = null;
+    }
+
+    /**
+     * Attempt to generate a ConcurrentModificationException in ArraySet.
+     * <p>
+     * ArraySet is explicitly documented to be non-thread-safe, yet it's easy to accidentally screw
+     * this up; ArraySet should (in the spirit of the core Java collection types) make an effort to
+     * catch this and throw ConcurrentModificationException instead of crashing somewhere in its
+     * internals.
+     */
+    @Test
+    public void testConcurrentModificationException() throws Exception {
+        final int testDurMs = 10_000;
+        System.out.println("Starting ArraySet concurrency test");
+        new Thread(() -> {
+            int i = 0;
+            while (mSet != null) {
+                try {
+                    mSet.add(String.format("key %d", i++));
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    Log.e(TAG, "concurrent modification uncaught, causing indexing failure", e);
+                    fail("Concurrent modification uncaught, causing indexing failure: " + e);
+                } catch (ClassCastException e) {
+                    Log.e(TAG, "concurrent modification uncaught, causing cache corruption", e);
+                    fail("Concurrent modification uncaught, causing cache corruption: " + e);
+                } catch (ConcurrentModificationException e) {
+                    System.out.println("[successfully caught CME at put #" + i
+                            + " size=" + (mSet == null ? "??" : String.valueOf(mSet.size())) + "]");
+                    if (i % 200 == 0) {
+                        System.out.print(".");
+                    }
+                }
+            }
+        }).start();
+        for (int i = 0; i < (testDurMs / 100); i++) {
+            try {
+                if (mSet.size() % 4 == 0) {
+                    mSet.clear();
+                }
+                System.out.print("X");
+            } catch (ArrayIndexOutOfBoundsException e) {
+                Log.e(TAG, "concurrent modification uncaught, causing indexing failure", e);
+                fail("Concurrent modification uncaught, causing indexing failure: " + e);
+            } catch (ClassCastException e) {
+                Log.e(TAG, "concurrent modification uncaught, causing cache corruption", e);
+                fail("Concurrent modification uncaught, causing cache corruption: " + e);
+            } catch (ConcurrentModificationException e) {
+                System.out.println(
+                        "[successfully caught CME at clear #" + i + " size=" + mSet.size() + "]");
+            }
+        }
+    }
+
+    /**
+     * Check to make sure the same operations behave as expected in a single thread.
+     */
+    @Test
+    public void testNonConcurrentAccesses() throws Exception {
+        for (int i = 0; i < 100000; i++) {
+            try {
+                mSet.add(String.format("key %d", i++));
+                if (i % 200 == 0) {
+                    System.out.print(".");
+                }
+                if (i % 500 == 0) {
+                    mSet.clear();
+                    System.out.print("X");
+                }
+            } catch (ConcurrentModificationException e) {
+                Log.e(TAG, "concurrent modification caught on single thread", e);
+                fail();
+            }
+        }
+    }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 4187c80..89523d6 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -322,6 +322,8 @@
         <permission name="android.permission.SET_WALLPAPER" />
         <permission name="android.permission.SET_WALLPAPER_COMPONENT" />
         <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" />
+        <!-- Permission required to test system only camera devices. -->
+        <permission name="android.permission.SYSTEM_CAMERA" />
         <!-- Permission required to test ExplicitHealthCheckServiceImpl. -->
         <permission name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"/>
     </privapp-permissions>
diff --git a/media/Android.bp b/media/Android.bp
index 29064ad..ef32239 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -67,6 +67,7 @@
         "apex/java/android/media/Session2Link.java",
         "apex/java/android/media/Session2Token.java",
     ],
+    path: "apex/java",
 }
 
 filegroup {
@@ -79,6 +80,7 @@
         "apex/java/android/media/IMediaSession2.aidl",
         "apex/java/android/media/IMediaSession2Service.aidl",
     ],
+    path: "apex/java",
 }
 
 filegroup {
@@ -98,6 +100,7 @@
         "apex/java/android/media/BufferingParams.java",
         "apex/java/android/media/ProxyDataSourceCallback.java",
     ],
+    path: "apex/java",
 }
 
 metalava_updatable_media_args = " --error UnhiddenSystemApi " +
diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java
index be0d966..792a2ba 100644
--- a/media/java/android/media/Metadata.java
+++ b/media/java/android/media/Metadata.java
@@ -272,15 +272,6 @@
     @UnsupportedAppUsage
     public Metadata() { }
 
-    // Have to declare protected for finalize() since it is protected
-    // in the base class Object.
-    @Override
-    protected void finalize() throws Throwable {
-        if (mParcel != null) {
-            mParcel.recycle();
-        }
-    }
-
     /**
      * Go over all the records, collecting metadata keys and records'
      * type field offset in the Parcel. These are stored in
@@ -427,10 +418,6 @@
             parcel.setDataPosition(pin);
             return false;
         }
-
-        if (mParcel != null) {
-            mParcel.recycle();
-        }
         mParcel = parcel;
         return true;
     }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bb9a5e4..c05609e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -169,6 +169,9 @@
     <uses-permission android:name="android.permission.MANAGE_SENSORS" />
     <uses-permission android:name="android.permission.MANAGE_AUDIO_POLICY" />
     <uses-permission android:name="android.permission.MANAGE_CAMERA" />
+    <!-- Permissions needed to test system only camera devices -->
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.SYSTEM_CAMERA" />
     <!-- Permission needed to enable/disable Bluetooth/Wifi -->
     <uses-permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" />
     <uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" />
diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp
new file mode 100644
index 0000000..3be7ca9
--- /dev/null
+++ b/packages/SoundPicker/Android.bp
@@ -0,0 +1,18 @@
+android_app {
+    name: "SoundPicker",
+    manifest: "AndroidManifest.xml",
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    platform_apis: true,
+    certificate: "media",
+    privileged: true,
+}
diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml
new file mode 100644
index 0000000..9d08182
--- /dev/null
+++ b/packages/SoundPicker/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.soundpicker"
+        android:sharedUserId="android.media">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+    <application
+            android:allowBackup="false"
+            android:supportsRtl="true">
+        <receiver android:name="RingtoneReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
+            </intent-filter>
+        </receiver>
+
+        <service android:name="RingtoneOverlayService" />
+
+        <activity android:name="RingtonePickerActivity"
+                android:theme="@style/PickerDialogTheme"
+                android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+                android:excludeFromRecents="true">
+            <intent-filter>
+                <action android:name="android.intent.action.RINGTONE_PICKER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/packages/SoundPicker/res/drawable/ic_add.xml b/packages/SoundPicker/res/drawable/ic_add.xml
new file mode 100644
index 0000000..22b3fe9
--- /dev/null
+++ b/packages/SoundPicker/res/drawable/ic_add.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2016 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="?android:attr/colorAccent"
+        android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/drawable/ic_add_padded.xml b/packages/SoundPicker/res/drawable/ic_add_padded.xml
new file mode 100644
index 0000000..c376867
--- /dev/null
+++ b/packages/SoundPicker/res/drawable/ic_add_padded.xml
@@ -0,0 +1,22 @@
+<!--
+    Copyright (C) 2017 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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+        android:drawable="@drawable/ic_add"
+        android:insetTop="4dp"
+        android:insetRight="4dp"
+        android:insetBottom="4dp"
+        android:insetLeft="4dp"/>
diff --git a/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml
new file mode 100644
index 0000000..6f91d77
--- /dev/null
+++ b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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.
+-->
+
+<!--
+     Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent.
+     Make the visibility to "gone" to prevent failures.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@null"
+        android:textColor="?android:attr/colorAccent"
+        android:gravity="center_vertical"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:drawableStart="@drawable/ic_add_padded"
+        android:drawablePadding="8dp"
+        android:ellipsize="marquee"
+        android:visibility="gone" />
diff --git a/packages/SoundPicker/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker/res/layout-watch/radio_with_work_badge.xml
new file mode 100644
index 0000000..0e11621
--- /dev/null
+++ b/packages/SoundPicker/res/layout-watch/radio_with_work_badge.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<com.android.providers.media.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    >
+
+    <CheckedTextView
+        android:id="@+id/checked_text_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:gravity="center_vertical"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+        android:drawablePadding="8dp"
+        android:ellipsize="marquee"
+        android:layout_toLeftOf="@+id/work_icon"
+        android:maxLines="3" />
+
+    <ImageView
+        android:id="@id/work_icon"
+        android:layout_width="18dp"
+        android:layout_height="18dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="20dp" />
+</com.android.providers.media.CheckedListItem>
diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml
new file mode 100644
index 0000000..14421c9
--- /dev/null
+++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground">
+
+<ImageView
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="24dp"
+        android:layout_marginLeft="24dp"
+        android:src="@drawable/ic_add" />
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/add_new_sound_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:text="@null"
+        android:textColor="?android:attr/colorAccent"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:maxLines="3"
+        android:gravity="center_vertical"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:drawablePadding="20dp"
+        android:ellipsize="marquee" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
new file mode 100644
index 0000000..e7d37ea
--- /dev/null
+++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<com.android.providers.media.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    >
+
+    <CheckedTextView
+        android:id="@+id/checked_text_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:gravity="center_vertical"
+        android:paddingStart="20dp"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+        android:drawablePadding="20dp"
+        android:ellipsize="marquee"
+        android:layout_toLeftOf="@+id/work_icon"
+        android:maxLines="3" />
+
+    <ImageView
+        android:id="@id/work_icon"
+        android:layout_width="18dp"
+        android:layout_height="18dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="20dp" />
+</com.android.providers.media.CheckedListItem>
diff --git a/packages/SoundPicker/res/raw/default_alarm_alert.ogg b/packages/SoundPicker/res/raw/default_alarm_alert.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker/res/raw/default_alarm_alert.ogg
diff --git a/packages/SoundPicker/res/raw/default_notification_sound.ogg b/packages/SoundPicker/res/raw/default_notification_sound.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker/res/raw/default_notification_sound.ogg
diff --git a/packages/SoundPicker/res/raw/default_ringtone.ogg b/packages/SoundPicker/res/raw/default_ringtone.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker/res/raw/default_ringtone.ogg
diff --git a/packages/SoundPicker/res/values-watch/config.xml b/packages/SoundPicker/res/values-watch/config.xml
new file mode 100644
index 0000000..0bc24fa
--- /dev/null
+++ b/packages/SoundPicker/res/values-watch/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds.  Do not translate.
+
+     NOTE: The naming convention is "config_camelCaseValue".  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
+    ringtone will be automatically selected when the picker is closed. -->
+    <bool name="config_showOkCancelButtons">false</bool>
+</resources>
diff --git a/packages/SoundPicker/res/values/config.xml b/packages/SoundPicker/res/values/config.xml
new file mode 100644
index 0000000..4e237a2
--- /dev/null
+++ b/packages/SoundPicker/res/values/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds.  Do not translate.
+
+     NOTE: The naming convention is "config_camelCaseValue".  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
+    ringtone will be automatically selected when the picker is closed. -->
+    <bool name="config_showOkCancelButtons">true</bool>
+</resources>
diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml
new file mode 100644
index 0000000..56ed5fd
--- /dev/null
+++ b/packages/SoundPicker/res/values/strings.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Choice in the ringtone picker.  If chosen, the default ringtone will be used. -->
+    <string name="ringtone_default">Default ringtone</string>
+
+    <!-- Choice in the notification sound picker.  If chosen, the default notification sound will be
+         used. -->
+    <string name="notification_sound_default">Default notification sound</string>
+
+    <!-- Choice in the alarm sound picker.  If chosen, the default alarm sound will be used. -->
+    <string name="alarm_sound_default">Default alarm sound</string>
+
+    <!-- Text for the RingtonePicker item that allows adding a new ringtone. -->
+    <string name="add_ringtone_text">Add ringtone</string>
+    <!-- Text for the RingtonePicker item that allows adding a new alarm. -->
+    <string name="add_alarm_text">Add alarm</string>
+    <!-- Text for the RingtonePicker item that allows adding a new notification. -->
+    <string name="add_notification_text">Add notification</string>
+    <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. -->
+    <string name="delete_ringtone_text">Delete</string>
+    <!-- Text for the Toast displayed when adding a custom ringtone fails. -->
+    <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
+    <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
+    <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
+</resources>
diff --git a/packages/SoundPicker/res/values/styles.xml b/packages/SoundPicker/res/values/styles.xml
new file mode 100644
index 0000000..d22d9c4
--- /dev/null
+++ b/packages/SoundPicker/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
+    </style>
+
+</resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
new file mode 100644
index 0000000..2d37b4c
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -0,0 +1,113 @@
+/*
+ * 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.soundpicker;
+
+import android.app.Service;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.provider.Settings.System;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Service to copy and set customization of default sounds
+ */
+public class RingtoneOverlayService extends Service {
+    private static final String TAG = "RingtoneOverlayService";
+    private static final boolean DEBUG = false;
+
+    @Override
+    public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
+        AsyncTask.execute(() -> {
+            updateRingtones();
+            stopSelf();
+        });
+
+        // Try again later if we are killed before we finish.
+        return Service.START_REDELIVER_INTENT;
+    }
+
+    @Override
+    public IBinder onBind(@Nullable final Intent intent) {
+        return null;
+    }
+
+    private void updateRingtones() {
+        copyResourceAndSetAsSound(R.raw.default_ringtone,
+                System.RINGTONE, Environment.DIRECTORY_RINGTONES);
+        copyResourceAndSetAsSound(R.raw.default_notification_sound,
+                System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS);
+        copyResourceAndSetAsSound(R.raw.default_alarm_alert,
+                System.ALARM_ALERT, Environment.DIRECTORY_ALARMS);
+    }
+
+    /* If the resource contains any data, copy a resource to the file system, scan it, and set the
+     * file URI as the default for a sound. */
+    private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name,
+            @NonNull final String subPath) {
+        final File destDir = Environment.getExternalStoragePublicDirectory(subPath);
+        if (!destDir.exists() && !destDir.mkdirs()) {
+            Log.e(TAG, "can't create " + destDir.getAbsolutePath());
+            return;
+        }
+
+        final File dest = new File(destDir, "default_" + name + ".ogg");
+        try (
+            InputStream is = getResources().openRawResource(id);
+            FileOutputStream os = new FileOutputStream(dest);
+        ) {
+            if (is.available() > 0) {
+                FileUtils.copy(is, os);
+                final Uri uri = scanFile(dest);
+                if (uri != null) {
+                    set(name, uri);
+                }
+            } else {
+                // TODO Shall we remove any former copied resource in this case and unset
+                // the defaults if we use this event a second time to clear the data?
+                if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to open resource for " + name + ": " + e);
+        }
+    }
+
+    private Uri scanFile(@NonNull final File file) {
+        return MediaStore.scanFile(this, file);
+    }
+
+    private void set(@NonNull final String name, @NonNull final Uri uri) {
+        final Uri settingUri = System.getUriFor(name);
+        RingtoneManager.setActualDefaultRingtoneUri(this,
+                RingtoneManager.getDefaultType(settingUri), uri);
+        System.putInt(getContentResolver(), name + "_set", 1);
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
new file mode 100644
index 0000000..4ba5146
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -0,0 +1,762 @@
+/*
+ * 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.soundpicker;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * The {@link RingtonePickerActivity} allows the user to choose one from all of the
+ * available ringtones. The chosen ringtone's URI will be persisted as a string.
+ *
+ * @see RingtoneManager#ACTION_RINGTONE_PICKER
+ */
+public final class RingtonePickerActivity extends AlertActivity implements
+        AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
+        AlertController.AlertParams.OnPrepareListViewListener {
+
+    private static final int POS_UNKNOWN = -1;
+
+    private static final String TAG = "RingtonePickerActivity";
+
+    private static final int DELAY_MS_SELECTION_PLAYED = 300;
+
+    private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
+
+    private static final String SAVE_CLICKED_POS = "clicked_pos";
+
+    private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
+
+    private static final int ADD_FILE_REQUEST_CODE = 300;
+
+    private RingtoneManager mRingtoneManager;
+    private int mType;
+
+    private Cursor mCursor;
+    private Handler mHandler;
+    private BadgedRingtoneAdapter mAdapter;
+
+    /** The position in the list of the 'Silent' item. */
+    private int mSilentPos = POS_UNKNOWN;
+
+    /** The position in the list of the 'Default' item. */
+    private int mDefaultRingtonePos = POS_UNKNOWN;
+
+    /** The position in the list of the ringtone to sample. */
+    private int mSampleRingtonePos = POS_UNKNOWN;
+
+    /** Whether this list has the 'Silent' item. */
+    private boolean mHasSilentItem;
+
+    /** The Uri to place a checkmark next to. */
+    private Uri mExistingUri;
+
+    /** The number of static items in the list. */
+    private int mStaticItemCount;
+
+    /** Whether this list has the 'Default' item. */
+    private boolean mHasDefaultItem;
+
+    /** The Uri to play when the 'Default' item is clicked. */
+    private Uri mUriForDefaultItem;
+
+    /** Id of the user to which the ringtone picker should list the ringtones */
+    private int mPickerUserId;
+
+    /** Context of the user specified by mPickerUserId */
+    private Context mTargetContext;
+
+    /**
+     * A Ringtone for the default ringtone. In most cases, the RingtoneManager
+     * will stop the previous ringtone. However, the RingtoneManager doesn't
+     * manage the default ringtone for us, so we should stop this one manually.
+     */
+    private Ringtone mDefaultRingtone;
+
+    /**
+     * The ringtone that's currently playing, unless the currently playing one is the default
+     * ringtone.
+     */
+    private Ringtone mCurrentRingtone;
+
+    /**
+     * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked).
+     */
+    private long mCheckedItemId = -1;
+
+    private int mAttributesFlags;
+
+    private boolean mShowOkCancelButtons;
+
+    /**
+     * Keep the currently playing ringtone around when changing orientation, so that it
+     * can be stopped later, after the activity is recreated.
+     */
+    private static Ringtone sPlayingRingtone;
+
+    private DialogInterface.OnClickListener mRingtoneClickListener =
+            new DialogInterface.OnClickListener() {
+
+        /*
+         * On item clicked
+         */
+        public void onClick(DialogInterface dialog, int which) {
+            if (which == mCursor.getCount() + mStaticItemCount) {
+                // The "Add new ringtone" item was clicked. Start a file picker intent to select
+                // only audio files (MIME type "audio/*")
+                final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+                chooseFile.setType("audio/*");
+                chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
+                        new String[] { "audio/*", "application/ogg" });
+                startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE);
+                return;
+            }
+
+            // Save the position of most recently clicked item
+            setCheckedItem(which);
+
+            // In the buttonless (watch-only) version, preemptively set our result since we won't
+            // have another chance to do so before the activity closes.
+            if (!mShowOkCancelButtons) {
+                setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+            }
+
+            // Play clip
+            playRingtone(which, 0);
+        }
+
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mHandler = new Handler();
+
+        Intent intent = getIntent();
+        mPickerUserId = UserHandle.myUserId();
+        mTargetContext = this;
+
+        // Get the types of ringtones to show
+        mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
+        initRingtoneManager();
+
+        /*
+         * Get whether to show the 'Default' item, and the URI to play when the
+         * default is clicked
+         */
+        mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+        mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+        if (mUriForDefaultItem == null) {
+            if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+                mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI;
+            } else if (mType == RingtoneManager.TYPE_ALARM) {
+                mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI;
+            } else if (mType == RingtoneManager.TYPE_RINGTONE) {
+                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+            } else {
+                // or leave it null for silence.
+                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+            }
+        }
+
+        // Get whether to show the 'Silent' item
+        mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+        // AudioAttributes flags
+        mAttributesFlags |= intent.getIntExtra(
+                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+                0 /*defaultValue == no flags*/);
+
+        mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
+
+        // The volume keys will control the stream that we are choosing a ringtone for
+        setVolumeControlStream(mRingtoneManager.inferStreamType());
+
+        // Get the URI whose list item should have a checkmark
+        mExistingUri = intent
+                .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+        // Create the list of ringtones and hold on to it so we can update later.
+        mAdapter = new BadgedRingtoneAdapter(this, mCursor,
+                /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId));
+        if (savedInstanceState != null) {
+            setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN));
+        }
+
+        final AlertController.AlertParams p = mAlertParams;
+        p.mAdapter = mAdapter;
+        p.mOnClickListener = mRingtoneClickListener;
+        p.mLabelColumn = COLUMN_LABEL;
+        p.mIsSingleChoice = true;
+        p.mOnItemSelectedListener = this;
+        if (mShowOkCancelButtons) {
+            p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
+            p.mPositiveButtonListener = this;
+            p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+            p.mPositiveButtonListener = this;
+        }
+        p.mOnPrepareListViewListener = this;
+
+        p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (p.mTitle == null) {
+          if (mType == RingtoneManager.TYPE_ALARM) {
+              p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm);
+          } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+              p.mTitle =
+                  getString(com.android.internal.R.string.ringtone_picker_title_notification);
+          } else {
+              p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
+          }
+        }
+
+        setupAlert();
+    }
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(SAVE_CLICKED_POS, getCheckedItem());
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) {
+            // Add the custom ringtone in a separate thread
+            final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() {
+                @Override
+                protected Uri doInBackground(Uri... params) {
+                    try {
+                        return mRingtoneManager.addCustomExternalRingtone(params[0], mType);
+                    } catch (IOException | IllegalArgumentException e) {
+                        Log.e(TAG, "Unable to add new ringtone", e);
+                    }
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Uri ringtoneUri) {
+                    if (ringtoneUri != null) {
+                        requeryForAdapter();
+                    } else {
+                        // Ringtone was not added, display error Toast
+                        Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            };
+            installTask.execute(data.getData());
+        }
+    }
+
+    // Disabled because context menus aren't Material Design :(
+    /*
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        int position = ((AdapterContextMenuInfo) menuInfo).position;
+
+        Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position));
+        if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) {
+            // It's a custom ringtone so we display the context menu
+            menu.setHeaderTitle(ringtone.getTitle(this));
+            menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text);
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case Menu.FIRST: {
+                int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
+                Uri deletedRingtoneUri = getRingtone(
+                        getRingtoneManagerPosition(deletedRingtonePos)).getUri();
+                if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) {
+                    requeryForAdapter();
+                } else {
+                    Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT)
+                            .show();
+                }
+                return true;
+            }
+            default: {
+                return false;
+            }
+        }
+    }
+    */
+
+    @Override
+    public void onDestroy() {
+        if (mCursor != null) {
+            mCursor.close();
+            mCursor = null;
+        }
+        super.onDestroy();
+    }
+
+    public void onPrepareListView(ListView listView) {
+        // Reset the static item count, as this method can be called multiple times
+        mStaticItemCount = 0;
+
+        if (mHasDefaultItem) {
+            mDefaultRingtonePos = addDefaultRingtoneItem(listView);
+
+            if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) {
+                setCheckedItem(mDefaultRingtonePos);
+            }
+        }
+
+        if (mHasSilentItem) {
+            mSilentPos = addSilentItem(listView);
+
+            // The 'Silent' item should use a null Uri
+            if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) {
+                setCheckedItem(mSilentPos);
+            }
+        }
+
+        if (getCheckedItem() == POS_UNKNOWN) {
+            setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri)));
+        }
+
+        // In the buttonless (watch-only) version, preemptively set our result since we won't
+        // have another chance to do so before the activity closes.
+        if (!mShowOkCancelButtons) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        }
+        // If external storage is available, add a button to install sounds from storage.
+        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            addNewSoundItem(listView);
+        }
+
+        // Enable context menu in ringtone items
+        registerForContextMenu(listView);
+    }
+
+    /**
+     * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
+     * selected item position to match the new position of the chosen sound.
+     *
+     * This should only need to happen after adding or removing a ringtone.
+     */
+    private void requeryForAdapter() {
+        // Refresh and set a new cursor, closing the old one.
+        initRingtoneManager();
+        mAdapter.changeCursor(mCursor);
+
+        // Update checked item location.
+        int checkedPosition = POS_UNKNOWN;
+        for (int i = 0; i < mAdapter.getCount(); i++) {
+            if (mAdapter.getItemId(i) == mCheckedItemId) {
+                checkedPosition = getListPosition(i);
+                break;
+            }
+        }
+        if (mHasSilentItem && checkedPosition == POS_UNKNOWN) {
+            checkedPosition = mSilentPos;
+        }
+        setCheckedItem(checkedPosition);
+        setupAlert();
+    }
+
+    /**
+     * Adds a static item to the top of the list. A static item is one that is not from the
+     * RingtoneManager.
+     *
+     * @param listView The ListView to add to.
+     * @param textResId The resource ID of the text for the item.
+     * @return The position of the inserted item.
+     */
+    private int addStaticItem(ListView listView, int textResId) {
+        TextView textView = (TextView) getLayoutInflater().inflate(
+                com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false);
+        textView.setText(textResId);
+        listView.addHeaderView(textView);
+        mStaticItemCount++;
+        return listView.getHeaderViewsCount() - 1;
+    }
+
+    private int addDefaultRingtoneItem(ListView listView) {
+        if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+            return addStaticItem(listView, R.string.notification_sound_default);
+        } else if (mType == RingtoneManager.TYPE_ALARM) {
+            return addStaticItem(listView, R.string.alarm_sound_default);
+        }
+
+        return addStaticItem(listView, R.string.ringtone_default);
+    }
+
+    private int addSilentItem(ListView listView) {
+        return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
+    }
+
+    private void addNewSoundItem(ListView listView) {
+        View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView,
+                false /* attachToRoot */);
+        TextView text = (TextView)view.findViewById(R.id.add_new_sound_text);
+
+        if (mType == RingtoneManager.TYPE_ALARM) {
+            text.setText(R.string.add_alarm_text);
+        } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+            text.setText(R.string.add_notification_text);
+        } else {
+            text.setText(R.string.add_ringtone_text);
+        }
+        listView.addFooterView(view);
+    }
+
+    private void initRingtoneManager() {
+        // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it
+        // causes unexpected behavior.
+        mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true);
+        if (mType != -1) {
+            mRingtoneManager.setType(mType);
+        }
+        mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL);
+    }
+
+    private Ringtone getRingtone(int ringtoneManagerPosition) {
+        if (ringtoneManagerPosition < 0) {
+            return null;
+        }
+        return mRingtoneManager.getRingtone(ringtoneManagerPosition);
+    }
+
+    private int getCheckedItem() {
+        return mAlertParams.mCheckedItem;
+    }
+
+    private void setCheckedItem(int pos) {
+        mAlertParams.mCheckedItem = pos;
+        mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos));
+    }
+
+    /*
+     * On click of Ok/Cancel buttons
+     */
+    public void onClick(DialogInterface dialog, int which) {
+        boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
+
+        // Stop playing the previous ringtone
+        mRingtoneManager.stopPreviousRingtone();
+
+        if (positiveResult) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        } else {
+            setResult(RESULT_CANCELED);
+        }
+
+        finish();
+    }
+
+    /*
+     * On item selected via keys
+     */
+    public void onItemSelected(AdapterView parent, View view, int position, long id) {
+        // footer view
+        if (position >= mCursor.getCount() + mStaticItemCount) {
+            return;
+        }
+
+        playRingtone(position, DELAY_MS_SELECTION_PLAYED);
+
+        // In the buttonless (watch-only) version, preemptively set our result since we won't
+        // have another chance to do so before the activity closes.
+        if (!mShowOkCancelButtons) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        }
+    }
+
+    public void onNothingSelected(AdapterView parent) {
+    }
+
+    private void playRingtone(int position, int delayMs) {
+        mHandler.removeCallbacks(this);
+        mSampleRingtonePos = position;
+        mHandler.postDelayed(this, delayMs);
+    }
+
+    public void run() {
+        stopAnyPlayingRingtone();
+        if (mSampleRingtonePos == mSilentPos) {
+            return;
+        }
+
+        Ringtone ringtone;
+        if (mSampleRingtonePos == mDefaultRingtonePos) {
+            if (mDefaultRingtone == null) {
+                mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
+            }
+           /*
+            * Stream type of mDefaultRingtone is not set explicitly here.
+            * It should be set in accordance with mRingtoneManager of this Activity.
+            */
+            if (mDefaultRingtone != null) {
+                mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType());
+            }
+            ringtone = mDefaultRingtone;
+            mCurrentRingtone = null;
+        } else {
+            ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
+            mCurrentRingtone = ringtone;
+        }
+
+        if (ringtone != null) {
+            if (mAttributesFlags != 0) {
+                ringtone.setAudioAttributes(
+                        new AudioAttributes.Builder(ringtone.getAudioAttributes())
+                                .setFlags(mAttributesFlags)
+                                .build());
+            }
+            ringtone.play();
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        if (!isChangingConfigurations()) {
+            stopAnyPlayingRingtone();
+        } else {
+            saveAnyPlayingRingtone();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (!isChangingConfigurations()) {
+            stopAnyPlayingRingtone();
+        }
+    }
+
+    private void setSuccessResultWithRingtone(Uri ringtoneUri) {
+      setResult(RESULT_OK,
+          new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
+    }
+
+    private Uri getCurrentlySelectedRingtoneUri() {
+      if (getCheckedItem() == mDefaultRingtonePos) {
+        // Use the default Uri that they originally gave us.
+        return mUriForDefaultItem;
+      } else if (getCheckedItem() == mSilentPos) {
+        // Use a null Uri for the 'Silent' item.
+        return null;
+      } else {
+        return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem()));
+      }
+    }
+
+    private void saveAnyPlayingRingtone() {
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            sPlayingRingtone = mDefaultRingtone;
+        } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
+            sPlayingRingtone = mCurrentRingtone;
+        }
+    }
+
+    private void stopAnyPlayingRingtone() {
+        if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
+            sPlayingRingtone.stop();
+        }
+        sPlayingRingtone = null;
+
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            mDefaultRingtone.stop();
+        }
+
+        if (mRingtoneManager != null) {
+            mRingtoneManager.stopPreviousRingtone();
+        }
+    }
+
+    private int getRingtoneManagerPosition(int listPos) {
+        return listPos - mStaticItemCount;
+    }
+
+    private int getListPosition(int ringtoneManagerPos) {
+
+        // If the manager position is -1 (for not found), return that
+        if (ringtoneManagerPos < 0) return ringtoneManagerPos;
+
+        return ringtoneManagerPos + mStaticItemCount;
+    }
+
+    private static class LocalizedCursor extends CursorWrapper {
+
+        final int mTitleIndex;
+        final Resources mResources;
+        String mNamePrefix;
+        final Pattern mSanitizePattern;
+
+        LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
+            super(cursor);
+            mTitleIndex = mCursor.getColumnIndex(columnLabel);
+            mResources = resources;
+            mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
+            if (mTitleIndex == -1) {
+                Log.e(TAG, "No index for column " + columnLabel);
+                mNamePrefix = null;
+            } else {
+                try {
+                    // Build the prefix for the name of the resource to look up
+                    // format is: "ResourcePackageName::ResourceTypeName/"
+                    // (the type name is expected to be "string" but let's not hardcode it).
+                    // Here we use an existing resource "notification_sound_default" which is
+                    // always expected to be found.
+                    mNamePrefix = String.format("%s:%s/%s",
+                            mResources.getResourcePackageName(R.string.notification_sound_default),
+                            mResources.getResourceTypeName(R.string.notification_sound_default),
+                            SOUND_NAME_RES_PREFIX);
+                } catch (NotFoundException e) {
+                    mNamePrefix = null;
+                }
+            }
+        }
+
+        /**
+         * Process resource name to generate a valid resource name.
+         * @param input
+         * @return a non-null String
+         */
+        private String sanitize(String input) {
+            if (input == null) {
+                return "";
+            }
+            return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase();
+        }
+
+        @Override
+        public String getString(int columnIndex) {
+            final String defaultName = mCursor.getString(columnIndex);
+            if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
+                return defaultName;
+            }
+            TypedValue value = new TypedValue();
+            try {
+                // the name currently in the database is used to derive a name to match
+                // against resource names in this package
+                mResources.getValue(mNamePrefix + sanitize(defaultName), value, false);
+            } catch (NotFoundException e) {
+                // no localized string, use the default string
+                return defaultName;
+            }
+            if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
+                Log.d(TAG, String.format("Replacing name %s with %s",
+                        defaultName, value.string.toString()));
+                return value.string.toString();
+            } else {
+                Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
+                return defaultName;
+            }
+        }
+    }
+
+    private class BadgedRingtoneAdapter extends CursorAdapter {
+        private final boolean mIsManagedProfile;
+
+        public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) {
+            super(context, cursor);
+            mIsManagedProfile = isManagedProfile;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            if (position < 0) {
+                return position;
+            }
+            return super.getItemId(position);
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            LayoutInflater inflater = LayoutInflater.from(context);
+            return inflater.inflate(R.layout.radio_with_work_badge, parent, false);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            // Set text as the title of the ringtone
+            ((TextView) view.findViewById(R.id.checked_text_view))
+                    .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX));
+
+            boolean isWorkRingtone = false;
+            if (mIsManagedProfile) {
+                /*
+                 * Display the work icon if the ringtone belongs to a work profile. We can tell that
+                 * a ringtone belongs to a work profile if the picker user is a managed profile, the
+                 * ringtone Uri is in external storage, and either the uri has no user id or has the
+                 * id of the picker user
+                 */
+                Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition());
+                int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId);
+                Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
+
+                if (uriUserId == mPickerUserId && uriWithoutUserId.toString()
+                        .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
+                    isWorkRingtone = true;
+                }
+            }
+
+            ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon);
+            if(isWorkRingtone) {
+                workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground(
+                        UserHandle.of(mPickerUserId), -1 /* density */));
+                workIcon.setVisibility(View.VISIBLE);
+            } else {
+                workIcon.setVisibility(View.GONE);
+            }
+        }
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneReceiver.java
new file mode 100644
index 0000000..9e1ba3a
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneReceiver.java
@@ -0,0 +1,37 @@
+/* //device/content/providers/media/src/com/android/providers/media/MediaScannerReceiver.java
+**
+** Copyright 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.soundpicker;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class RingtoneReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
+            initResourceRingtones(context);
+        }
+    }
+
+    private void initResourceRingtones(Context context) {
+        context.startService(
+                new Intent(context, RingtoneOverlayService.class));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 10d132a..45126f3 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -19,16 +19,12 @@
 import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
 import static android.telephony.PhoneStateListener.LISTEN_NONE;
 
-import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
-import android.os.SystemProperties;
-import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
@@ -37,19 +33,17 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import androidx.annotation.VisibleForTesting;
-
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
 import com.android.settingslib.WirelessUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
+import androidx.annotation.VisibleForTesting;
+
 /**
  * Controller that generates text including the carrier names and/or the status of all the SIM
  * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
@@ -72,8 +66,6 @@
     private Context mContext;
     private CharSequence mSeparator;
     private WakefulnessLifecycle mWakefulnessLifecycle;
-    @VisibleForTesting
-    protected boolean mDisplayOpportunisticSubscriptionCarrierText;
     private final WakefulnessLifecycle.Observer mWakefulnessObserver =
             new WakefulnessLifecycle.Observer() {
                 @Override
@@ -174,9 +166,6 @@
         mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
                 Context.TELEPHONY_SERVICE)).getPhoneCount();
         mSimErrorState = new boolean[mSimSlotsNumber];
-        updateDisplayOpportunisticSubscriptionCarrierText(SystemProperties.getBoolean(
-                TelephonyProperties.DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME,
-                false));
     }
 
     /**
@@ -253,63 +242,8 @@
         }
     }
 
-    /**
-     * @param subscriptions
-     */
-    private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
-        if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
-            SubscriptionInfo info1 = subscriptions.get(0);
-            SubscriptionInfo info2 = subscriptions.get(1);
-            if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
-                // If both subscriptions are primary, show both.
-                if (!info1.isOpportunistic() && !info2.isOpportunistic()) return;
-
-                // If carrier required, always show signal bar of primary subscription.
-                // Otherwise, show whichever subscription is currently active for Internet.
-                boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
-                        .getBoolean(CarrierConfigManager
-                        .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
-                if (alwaysShowPrimary) {
-                    subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
-                } else {
-                    subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
-                            ? info2 : info1);
-                }
-
-            }
-        }
-    }
-
-    /**
-     * updates if opportunistic sub carrier text should be displayed or not
-     *
-     */
-    @VisibleForTesting
-    public void updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable) {
-        mDisplayOpportunisticSubscriptionCarrierText = isEnable;
-    }
-
     protected List<SubscriptionInfo> getSubscriptionInfo() {
-        List<SubscriptionInfo> subs;
-        if (mDisplayOpportunisticSubscriptionCarrierText) {
-            SubscriptionManager subscriptionManager = ((SubscriptionManager) mContext
-                    .getSystemService(
-                    Context.TELEPHONY_SUBSCRIPTION_SERVICE));
-            subs = subscriptionManager.getActiveSubscriptionInfoList(false);
-            if (subs == null) {
-                subs = new ArrayList<>();
-            } else {
-                filterMobileSubscriptionInSameGroup(subs);
-            }
-        } else {
-            subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
-            if (subs == null) {
-                subs = new ArrayList<>();
-            } else {
-                filterMobileSubscriptionInSameGroup(subs);
-            }
-        }
-        return subs;
+        return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
     }
 
     protected void updateCarrierText() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8c5374a..5d6cc83 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -30,6 +30,7 @@
 import static android.os.BatteryManager.EXTRA_STATUS;
 import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
 
+import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -77,6 +78,7 @@
 import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
@@ -261,6 +263,7 @@
     private boolean mLogoutEnabled;
     // If the user long pressed the lock icon, disabling face auth for the current session.
     private boolean mLockIconPressed;
+    private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     /**
      * Short delay before restarting biometric authentication after a successful try
@@ -406,9 +409,11 @@
                 }
             };
 
-    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+    @VisibleForTesting
+    public PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
         @Override
         public void onActiveDataSubscriptionIdChanged(int subId) {
+            mActiveMobileDataSubscription = subId;
             mHandler.sendEmptyMessage(MSG_SIM_SUBSCRIPTION_INFO_CHANGED);
         }
     };
@@ -511,7 +516,9 @@
         }
     }
 
-    /** @return List of SubscriptionInfo records, maybe empty but never null */
+    /**
+     * @return List of SubscriptionInfo records, maybe empty but never null.
+     */
     public List<SubscriptionInfo> getSubscriptionInfo(boolean forceReload) {
         List<SubscriptionInfo> sil = mSubscriptionInfo;
         if (sil == null || forceReload) {
@@ -523,7 +530,42 @@
         } else {
             mSubscriptionInfo = sil;
         }
-        return mSubscriptionInfo;
+        return new ArrayList<>(mSubscriptionInfo);
+    }
+
+    /**
+     * This method returns filtered list of SubscriptionInfo from {@link #getSubscriptionInfo}.
+     * above. Maybe empty but never null.
+     *
+     * In DSDS mode if both subscriptions are grouped and one is opportunistic, we filter out one
+     * of them based on carrier config. e.g. In this case we should only show one carrier name
+     * on the status bar and quick settings.
+     */
+    public List<SubscriptionInfo> getFilteredSubscriptionInfo(boolean forceReload) {
+        List<SubscriptionInfo> subscriptions = getSubscriptionInfo(false);
+        if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
+            SubscriptionInfo info1 = subscriptions.get(0);
+            SubscriptionInfo info2 = subscriptions.get(1);
+            if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
+                // If both subscriptions are primary, show both.
+                if (!info1.isOpportunistic() && !info2.isOpportunistic()) return subscriptions;
+
+                // If carrier required, always show signal bar of primary subscription.
+                // Otherwise, show whichever subscription is currently active for Internet.
+                boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
+                        .getBoolean(CarrierConfigManager
+                        .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
+                if (alwaysShowPrimary) {
+                    subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
+                } else {
+                    subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
+                            ? info2 : info1);
+                }
+
+            }
+        }
+
+        return subscriptions;
     }
 
     @Override
@@ -2700,6 +2742,7 @@
                 pw.println("    " + mSubscriptionInfo.get(i));
             }
         }
+        pw.println("  Current active data subId=" + mActiveMobileDataSubscription);
         pw.println("  Service states:");
         for (int subId : mServiceStates.keySet()) {
             pw.println("    " + subId + "=" + mServiceStates.get(subId));
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 7e3b423..d5c928b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -140,11 +140,12 @@
             LockPatternUtils lockPatternUtils,  ViewGroup container,
             DismissCallbackRegistry dismissCallbackRegistry,
             KeyguardBouncer.BouncerExpansionCallback expansionCallback,
-            FalsingManager falsingManager) {
+            FalsingManager falsingManager, KeyguardBypassController bypassController) {
         return new KeyguardBouncer(context, callback, lockPatternUtils, container,
                 dismissCallbackRegistry, falsingManager,
                 expansionCallback, UnlockMethodCache.getInstance(context),
-                KeyguardUpdateMonitor.getInstance(context), new Handler(Looper.getMainLooper()));
+                KeyguardUpdateMonitor.getInstance(context), bypassController,
+                new Handler(Looper.getMainLooper()));
     }
 
     public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index d1b3c3c..2a5ccdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -134,7 +134,7 @@
 
     private void updateText() {
         CharSequence displayText = null;
-        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
         final int N = subs.size();
         for (int i = 0; i < N; i++) {
             int subId = subs.get(i).getSubscriptionId();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index c4d346c..dc9b373 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -77,6 +77,7 @@
                 }
             };
     private final Runnable mRemoveViewRunnable = this::removeView;
+    private final KeyguardBypassController mKeyguardBypassController;
     protected KeyguardHostView mKeyguardView;
     private final Runnable mResetRunnable = ()-> {
         if (mKeyguardView != null) {
@@ -97,7 +98,8 @@
             LockPatternUtils lockPatternUtils, ViewGroup container,
             DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
             BouncerExpansionCallback expansionCallback, UnlockMethodCache unlockMethodCache,
-            KeyguardUpdateMonitor keyguardUpdateMonitor, Handler handler) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            KeyguardBypassController keyguardBypassController, Handler handler) {
         mContext = context;
         mCallback = callback;
         mLockPatternUtils = lockPatternUtils;
@@ -109,6 +111,7 @@
         mHandler = handler;
         mUnlockMethodCache = unlockMethodCache;
         mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+        mKeyguardBypassController = keyguardBypassController;
     }
 
     public void show(boolean resetSecuritySelection) {
@@ -171,7 +174,8 @@
         // Split up the work over multiple frames.
         DejankUtils.removeCallbacks(mResetRunnable);
         if (mUnlockMethodCache.isFaceAuthEnabled() && !needsFullscreenBouncer()
-                && !mKeyguardUpdateMonitor.userNeedsStrongAuth()) {
+                && !mKeyguardUpdateMonitor.userNeedsStrongAuth()
+                && !mKeyguardBypassController.getBypassEnabled()) {
             mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
         } else {
             DejankUtils.postAfterTraversal(mShowRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3508c90..ccb85fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -221,7 +221,7 @@
         mBiometricUnlockController = biometricUnlockController;
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
-                mExpansionCallback, falsingManager);
+                mExpansionCallback, falsingManager, bypassController);
         mNotificationPanelView = notificationPanelView;
         notificationPanelView.addExpansionListener(this);
         mBypassController = bypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index 0d6178b..f2c0434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -97,7 +97,7 @@
         boolean allSimsMissing = true;
         CharSequence displayText = null;
 
-        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
         final int N = subs.size();
         for (int i = 0; i < N; i++) {
             int subId = subs.get(i).getSubscriptionId();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 0044ca7..1ae1b97 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -70,15 +70,10 @@
     private static final CharSequence AIRPLANE_MODE_TEXT = "Airplane mode";
     private static final String TEST_CARRIER = "TEST_CARRIER";
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
-    private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
     private static final int TEST_CARRIER_ID = 1;
     private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(0, "", 0,
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
-            DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
-            TEST_CARRIER_ID, 0);
-    private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(0, "", 0,
-            TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
-            DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
+            DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, null,
             TEST_CARRIER_ID, 0);
     private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
@@ -125,7 +120,6 @@
                 mKeyguardUpdateMonitor);
         // This should not start listening on any of the real dependencies
         mCarrierTextController.setListening(mCarrierTextCallback);
-        mCarrierTextController.updateDisplayOpportunisticSubscriptionCarrierText(false);
     }
 
     @Test
@@ -134,7 +128,7 @@
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
         when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(IccCardConstants.State.READY);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
@@ -154,7 +148,7 @@
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
         when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSimState(1)).thenReturn(
                 IccCardConstants.State.CARD_IO_ERROR);
@@ -178,7 +172,7 @@
     @Test
     public void testWrongSlots() {
         reset(mCarrierTextCallback);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
                 new ArrayList<>());
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
                 IccCardConstants.State.CARD_IO_ERROR);
@@ -192,7 +186,7 @@
     @Test
     public void testMoreSlotsThanSubs() {
         reset(mCarrierTextCallback);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
                 new ArrayList<>());
 
         // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
@@ -242,7 +236,7 @@
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
@@ -266,7 +260,7 @@
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION_ROAMING);
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
@@ -287,7 +281,7 @@
     @Test
     public void testCreateInfo_noSubscriptions() {
         reset(mCarrierTextCallback);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
                 new ArrayList<>());
 
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
@@ -311,7 +305,7 @@
         list.add(TEST_SUBSCRIPTION);
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
@@ -336,7 +330,7 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt()))
                 .thenReturn(IccCardConstants.State.READY)
                 .thenReturn(IccCardConstants.State.NOT_READY);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
@@ -361,7 +355,7 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt()))
                 .thenReturn(IccCardConstants.State.NOT_READY)
                 .thenReturn(IccCardConstants.State.READY);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
@@ -388,7 +382,7 @@
                 .thenReturn(IccCardConstants.State.READY)
                 .thenReturn(IccCardConstants.State.NOT_READY)
                 .thenReturn(IccCardConstants.State.READY);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
@@ -403,30 +397,6 @@
                 captor.getValue().carrierText);
     }
 
-    @Test
-    public void testCarrierText_GroupedSubWithOpportunisticCarrierText() {
-        reset(mCarrierTextCallback);
-        List<SubscriptionInfo> list = new ArrayList<>();
-        list.add(TEST_SUBSCRIPTION);
-        list.add(TEST_SUBSCRIPTION_2);
-        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
-            .thenReturn(IccCardConstants.State.READY);
-
-        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
-        mCarrierTextController.updateDisplayOpportunisticSubscriptionCarrierText(true);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
-
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
-                ArgumentCaptor.forClass(
-                CarrierTextController.CarrierTextCallbackInfo.class);
-
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
-        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
-
-        assertEquals(TEST_CARRIER_2, captor.getValue().carrierText);
-    }
-
     public static class TestCarrierTextController extends CarrierTextController {
         private KeyguardUpdateMonitor mKUM;
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index db6177a..a3cb6c0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -16,15 +16,18 @@
 
 package com.android.keyguard;
 
+import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
+import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -42,6 +45,7 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -62,6 +66,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @SmallTest
@@ -73,7 +79,18 @@
 // new tests.
 @RunWithLooper(setAsMainLooper = true)
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
-
+    private static final String TEST_CARRIER = "TEST_CARRIER";
+    private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
+    private static final int TEST_CARRIER_ID = 1;
+    private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
+    private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "", 0,
+            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
+            TEST_CARRIER_ID, 0);
+    private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(2, "", 0,
+            TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
+            TEST_CARRIER_ID, 0);
     @Mock
     private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
     @Mock
@@ -92,6 +109,8 @@
     private DevicePolicyManager mDevicePolicyManager;
     @Mock
     private KeyguardBypassController mKeyguardBypassController;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
     private TestableLooper mTestableLooper;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
@@ -119,6 +138,7 @@
         context.addMockSystemService(FaceManager.class, mFaceManager);
         context.addMockSystemService(UserManager.class, mUserManager);
         context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
+        context.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
 
         mTestableLooper = TestableLooper.get(this);
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context);
@@ -441,6 +461,22 @@
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
+    @Test
+    public void testGetSubscriptionInfo_whenInGroupedSubWithOpportunistic() {
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION_2);
+        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
+        mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged(
+                TEST_SUBSCRIPTION_2.getSubscriptionId());
+        mTestableLooper.processAllMessages();
+
+        List<SubscriptionInfo> listToVerify = mKeyguardUpdateMonitor
+                .getFilteredSubscriptionInfo(false);
+        assertThat(listToVerify.size()).isEqualTo(1);
+        assertThat(listToVerify.get(0)).isEqualTo(TEST_SUBSCRIPTION_2);
+    }
+
     private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
         int subscription = simInited
                 ? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 907e695..cd60e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
@@ -84,6 +85,8 @@
     @Mock
     private UnlockMethodCache mUnlockMethodCache;
     @Mock
+    private KeyguardBypassController mKeyguardBypassController;
+    @Mock
     private Handler mHandler;
 
     private KeyguardBouncer mBouncer;
@@ -98,7 +101,8 @@
         when(mKeyguardHostView.getHeight()).thenReturn(500);
         mBouncer = new KeyguardBouncer(getContext(), mViewMediatorCallback,
                 mLockPatternUtils, container, mDismissCallbackRegistry, mFalsingManager,
-                mExpansionCallback, mUnlockMethodCache, mKeyguardUpdateMonitor, mHandler) {
+                mExpansionCallback, mUnlockMethodCache, mKeyguardUpdateMonitor,
+                mKeyguardBypassController, mHandler) {
             @Override
             protected void inflateView() {
                 super.inflateView();
@@ -391,6 +395,15 @@
     }
 
     @Test
+    public void testShow_delaysIfFaceAuthIsRunning_unlessBypass() {
+        when(mUnlockMethodCache.isFaceAuthEnabled()).thenReturn(true);
+        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
+        mBouncer.show(true /* reset */);
+
+        verify(mHandler, never()).postDelayed(any(), anyLong());
+    }
+
+    @Test
     public void testRegisterUpdateMonitorCallback() {
         verify(mKeyguardUpdateMonitor).registerCallback(any());
     }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 0748279..9936d73 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -1188,6 +1188,7 @@
     private static boolean isNotification(int usageHint) {
         switch (usageHint) {
             case AudioAttributes.USAGE_NOTIFICATION:
+            case AudioAttributes.USAGE_NOTIFICATION_EVENT:
             case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
             case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
             case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5465309..c0af814 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -65,7 +65,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.MessageQueue;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
@@ -101,11 +100,8 @@
 
 import dalvik.system.VMRuntime;
 
-import libcore.io.IoUtils;
-
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
@@ -2087,10 +2083,10 @@
                     }
                 }
             }
-            if (lrui <= mLruProcessActivityStart) {
+            if (lrui < mLruProcessActivityStart) {
                 mLruProcessActivityStart--;
             }
-            if (lrui <= mLruProcessServiceStart) {
+            if (lrui < mLruProcessServiceStart) {
                 mLruProcessServiceStart--;
             }
             mLruProcesses.remove(lrui);
@@ -2622,7 +2618,7 @@
                         if (!moved) {
                             // Goes to the end of the group.
                             mLruProcesses.remove(i);
-                            mLruProcesses.add(endIndex - 1, subProc);
+                            mLruProcesses.add(endIndex, subProc);
                             if (DEBUG_LRU) Slog.d(TAG_LRU,
                                     "Moving " + subProc
                                             + " from position " + i + " to end of group @ "
@@ -2867,15 +2863,6 @@
                     pos--;
                 }
                 mLruProcesses.add(pos, app);
-                if (pos == mLruProcessActivityStart) {
-                    mLruProcessActivityStart++;
-                }
-                if (pos == mLruProcessServiceStart) {
-                    // Unless {@code #hasService} is implemented, currently the starting position
-                    // for activity and service are the same, so the incoming position may equal to
-                    // the starting position of service.
-                    mLruProcessServiceStart++;
-                }
                 // If this process is part of a group, need to pull up any other processes
                 // in that group to be with it.
                 int endIndex = pos - 1;
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 89a5305..4c93564 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -142,11 +142,12 @@
         synchronized (mLock) {
             UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
+                Slog.d(LOG_TAG, "Unable to bind TextClassifierService at suggestSelection.");
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
                 userState.mService.onSuggestSelection(sessionId, request, callback);
             } else {
-                userState.mPendingRequests.add(new PendingRequest(
+                userState.mPendingRequests.add(new PendingRequest("suggestSelection",
                         () -> onSuggestSelection(sessionId, request, callback),
                         callback::onFailure, callback.asBinder(), this, userState));
             }
@@ -166,11 +167,12 @@
         synchronized (mLock) {
             UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
+                Slog.d(LOG_TAG, "Unable to bind TextClassifierService at classifyText.");
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
                 userState.mService.onClassifyText(sessionId, request, callback);
             } else {
-                userState.mPendingRequests.add(new PendingRequest(
+                userState.mPendingRequests.add(new PendingRequest("classifyText",
                         () -> onClassifyText(sessionId, request, callback),
                         callback::onFailure, callback.asBinder(), this, userState));
             }
@@ -190,11 +192,12 @@
         synchronized (mLock) {
             UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
+                Slog.d(LOG_TAG, "Unable to bind TextClassifierService at generateLinks.");
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
                 userState.mService.onGenerateLinks(sessionId, request, callback);
             } else {
-                userState.mPendingRequests.add(new PendingRequest(
+                userState.mPendingRequests.add(new PendingRequest("generateLinks",
                         () -> onGenerateLinks(sessionId, request, callback),
                         callback::onFailure, callback.asBinder(), this, userState));
             }
@@ -214,7 +217,7 @@
             if (userState.isBoundLocked()) {
                 userState.mService.onSelectionEvent(sessionId, event);
             } else {
-                userState.mPendingRequests.add(new PendingRequest(
+                userState.mPendingRequests.add(new PendingRequest("selectionEvent",
                         () -> onSelectionEvent(sessionId, event),
                         null /* onServiceFailure */, null /* binder */, this, userState));
             }
@@ -238,7 +241,7 @@
             if (userState.isBoundLocked()) {
                 userState.mService.onTextClassifierEvent(sessionId, event);
             } else {
-                userState.mPendingRequests.add(new PendingRequest(
+                userState.mPendingRequests.add(new PendingRequest("textClassifierEvent",
                         () -> onTextClassifierEvent(sessionId, event),
                         null /* onServiceFailure */, null /* binder */, this, userState));
             }
@@ -258,11 +261,12 @@
         synchronized (mLock) {
             UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
+                Slog.d(LOG_TAG, "Unable to bind TextClassifierService at detectLanguage.");
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
                 userState.mService.onDetectLanguage(sessionId, request, callback);
             } else {
-                userState.mPendingRequests.add(new PendingRequest(
+                userState.mPendingRequests.add(new PendingRequest("detectLanguage",
                         () -> onDetectLanguage(sessionId, request, callback),
                         callback::onFailure, callback.asBinder(), this, userState));
             }
@@ -282,11 +286,13 @@
         synchronized (mLock) {
             UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
+                Slog.d(LOG_TAG,
+                        "Unable to bind TextClassifierService at suggestConversationActions.");
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
                 userState.mService.onSuggestConversationActions(sessionId, request, callback);
             } else {
-                userState.mPendingRequests.add(new PendingRequest(
+                userState.mPendingRequests.add(new PendingRequest("suggestConversationActions",
                         () -> onSuggestConversationActions(sessionId, request, callback),
                         callback::onFailure, callback.asBinder(), this, userState));
             }
@@ -309,7 +315,7 @@
                         classificationContext, sessionId);
                 mSessionUserIds.put(sessionId, userId);
             } else {
-                userState.mPendingRequests.add(new PendingRequest(
+                userState.mPendingRequests.add(new PendingRequest("createTextClassificationSession",
                         () -> onCreateTextClassificationSession(classificationContext, sessionId),
                         null /* onServiceFailure */, null /* binder */, this, userState));
             }
@@ -332,9 +338,10 @@
                 userState.mService.onDestroyTextClassificationSession(sessionId);
                 mSessionUserIds.remove(sessionId);
             } else {
-                userState.mPendingRequests.add(new PendingRequest(
-                        () -> onDestroyTextClassificationSession(sessionId),
-                        null /* onServiceFailure */, null /* binder */, this, userState));
+                userState.mPendingRequests.add(
+                        new PendingRequest("destroyTextClassificationSession",
+                                () -> onDestroyTextClassificationSession(sessionId),
+                                null /* onServiceFailure */, null /* binder */, this, userState));
             }
         }
     }
@@ -379,6 +386,7 @@
 
     private static final class PendingRequest implements IBinder.DeathRecipient {
 
+        @Nullable private final String mName;
         @Nullable private final IBinder mBinder;
         @NonNull private final Runnable mRequest;
         @Nullable private final Runnable mOnServiceFailure;
@@ -394,11 +402,12 @@
          * @param service
          * @param owningUser
          */
-        PendingRequest(
+        PendingRequest(@Nullable String name,
                 @NonNull ThrowingRunnable request, @Nullable ThrowingRunnable onServiceFailure,
                 @Nullable IBinder binder,
                 TextClassificationManagerService service,
                 UserState owningUser) {
+            mName = name;
             mRequest =
                     logOnFailure(Preconditions.checkNotNull(request), "handling pending request");
             mOnServiceFailure =
@@ -499,6 +508,8 @@
                     request.mRequest.run();
                 } else {
                     if (request.mOnServiceFailure != null) {
+                        Slog.d(LOG_TAG, "Unable to bind TextClassifierService for PendingRequest "
+                                + request.mName);
                         request.mOnServiceFailure.run();
                     }
                 }
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index f51d4c4..1c015d0 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -159,11 +159,23 @@
         }
 
         // STEP 2: Resolve launch windowing mode.
-        // STEP 2.1: Determine if any parameter has specified initial bounds. That might be the
-        // launch bounds from activity options, or size/gravity passed in layout. It also treats the
-        // launch windowing mode in options as a suggestion for future resolution.
+        // STEP 2.1: Determine if any parameter can specify initial bounds/windowing mode. That
+        // might be the launch bounds from activity options, or size/gravity passed in layout. It
+        // also treats the launch windowing mode in options and source activity windowing mode in
+        // some cases as a suggestion for future resolution.
         int launchMode = options != null ? options.getLaunchWindowingMode()
                 : WINDOWING_MODE_UNDEFINED;
+        // In some cases we want to use the source's windowing mode as the default value, e.g. when
+        // source is a freeform window in a fullscreen display launching an activity on the same
+        // display.
+        if (launchMode == WINDOWING_MODE_UNDEFINED
+                && canInheritWindowingModeFromSource(display, source)) {
+            launchMode = source.getWindowingMode();
+            if (DEBUG) {
+                appendLog("inherit-from-source="
+                        + WindowConfiguration.windowingModeToString(launchMode));
+            }
+        }
         // hasInitialBounds is set if either activity options or layout has specified bounds. If
         // that's set we'll skip some adjustments later to avoid overriding the initial bounds.
         boolean hasInitialBounds = false;
@@ -342,6 +354,31 @@
                 ? displayId : DEFAULT_DISPLAY;
     }
 
+    private boolean canInheritWindowingModeFromSource(@NonNull ActivityDisplay display,
+            @Nullable ActivityRecord source) {
+        if (source == null) {
+            return false;
+        }
+
+        // There is not really any strong reason to tie the launching windowing mode and the source
+        // on freeform displays. The launching windowing mode is more tied to the content of the new
+        // activities.
+        if (display.inFreeformWindowingMode()) {
+            return false;
+        }
+
+        final int sourceWindowingMode = source.getWindowingMode();
+        if (sourceWindowingMode != WINDOWING_MODE_FULLSCREEN
+                && sourceWindowingMode != WINDOWING_MODE_FREEFORM) {
+            return false;
+        }
+
+        // Only inherit windowing mode if both source and target activities are on the same display.
+        // Otherwise we may have unintended freeform windows showing up if an activity in freeform
+        // window launches an activity on a fullscreen display by specifying display ID.
+        return display.mDisplayId == source.getDisplayId();
+    }
+
     private boolean canApplyFreeformWindowPolicy(@NonNull ActivityDisplay display, int launchMode) {
         return mSupervisor.mService.mSupportsFreeformWindowManagement
                 && (display.inFreeformWindowingMode() || launchMode == WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index bcff704..e6c9b9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -249,6 +249,20 @@
     }
 
     @Test
+    public void testInheritsFreeformModeFromSourceOnFullscreenDisplay() {
+        final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+                WINDOWING_MODE_FULLSCREEN);
+        final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+        source.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+                mActivity, source, /* options */ null, mCurrent, mResult));
+
+        assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+                WINDOWING_MODE_FULLSCREEN);
+    }
+
+    @Test
     public void testKeepsPictureInPictureLaunchModeInOptions() {
         final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
                 WINDOWING_MODE_FREEFORM);
@@ -571,6 +585,23 @@
     }
 
     @Test
+    public void testRespectsLaunchBoundsWithFreeformSourceOnFullscreenDisplay() {
+        final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+                WINDOWING_MODE_FULLSCREEN);
+        final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+        source.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        final Rect expected = new Rect(0, 0, 150, 150);
+        options.setLaunchBounds(expected);
+
+        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+                mActivity, source, options, mCurrent, mResult));
+
+        assertEquals(expected, mResult.mBounds);
+    }
+
+    @Test
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_LeftGravity() {
         final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
                 WINDOWING_MODE_FREEFORM);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4fcda63..7d9462a 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1026,6 +1026,18 @@
             "call_forwarding_map_non_number_to_voicemail_bool";
 
     /**
+     * When {@code true}, the phone will always tell the IMS stack to keep RTT enabled and
+     * determine on a per-call basis (based on extras from the dialer app) whether a call should be
+     * an RTT call or not.
+     *
+     * When {@code false}, the old behavior is used, where the toggle in accessibility settings is
+     * used to set the IMS stack's RTT enabled state.
+     * @hide
+     */
+    public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL =
+            "ignore_rtt_mode_setting_bool";
+
+    /**
      * Determines whether conference calls are supported by a carrier.  When {@code true},
      * conference calling is supported, {@code false otherwise}.
      */
@@ -3306,6 +3318,7 @@
         sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
         sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
         sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false);
+        sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
         sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 36e8123..bb2269f 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -551,7 +551,6 @@
      *
      * @param context Context of the application to check.
      * @return whether the app is authorized to manage this subscription per its metadata.
-     * @throws UnsupportedOperationException if this subscription is not embedded.
      * @hide
      * @deprecated - Do not use.
      */
@@ -567,15 +566,11 @@
      * @param context Any context.
      * @param packageName Package name of the app to check.
      * @return whether the app is authorized to manage this subscription per its metadata.
-     * @throws UnsupportedOperationException if this subscription is not embedded.
      * @hide
      * @deprecated - Do not use.
      */
     @Deprecated
     public boolean canManageSubscription(Context context, String packageName) {
-        if (!isEmbedded()) {
-            throw new UnsupportedOperationException("Not an embedded subscription");
-        }
         List<UiccAccessRule> allAccessRules = getAllAccessRules();
         if (allAccessRules == null) {
             return false;
@@ -606,9 +601,6 @@
      */
     @SystemApi
     public @Nullable List<UiccAccessRule> getAccessRules() {
-        if (!isEmbedded()) {
-            throw new UnsupportedOperationException("Not an embedded subscription");
-        }
         if (mNativeAccessRules == null) return null;
         return Arrays.asList(mNativeAccessRules);
     }
@@ -619,9 +611,6 @@
      * @hide
      */
     public @Nullable List<UiccAccessRule> getAllAccessRules() {
-        if (!isEmbedded()) {
-            throw new UnsupportedOperationException("Not an embedded subscription");
-        }
         List<UiccAccessRule> merged = new ArrayList<>();
         if (mNativeAccessRules != null) merged.addAll(getAccessRules());
         if (mCarrierConfigAccessRules != null) {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index a84c916..26574c4 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2588,7 +2588,6 @@
      *
      * @param info The subscription to check.
      * @return whether the app is authorized to manage this subscription per its metadata.
-     * @throws IllegalArgumentException if this subscription is not embedded.
      */
     public boolean canManageSubscription(SubscriptionInfo info) {
         return canManageSubscription(info, mContext.getPackageName());
@@ -2604,13 +2603,9 @@
      * @param info The subscription to check.
      * @param packageName Package name of the app to check.
      * @return whether the app is authorized to manage this subscription per its access rules.
-     * @throws IllegalArgumentException if this subscription is not embedded.
      * @hide
      */
     public boolean canManageSubscription(SubscriptionInfo info, String packageName) {
-        if (!info.isEmbedded()) {
-            throw new IllegalArgumentException("Not an embedded subscription");
-        }
         if (info.getAllAccessRules() == null) {
             return false;
         }
@@ -3012,7 +3007,7 @@
         // to the caller.
         boolean hasCarrierPrivilegePermission = TelephonyManager.from(mContext)
                 .hasCarrierPrivileges(info.getSubscriptionId())
-                || (info.isEmbedded() && canManageSubscription(info));
+                || canManageSubscription(info);
         return hasCarrierPrivilegePermission;
     }