Merge "OP_REQUEST_INSTALL_PACKAGES denied by default" into qt-dev
diff --git a/api/current.txt b/api/current.txt
index bc634c6..535fbbcf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44381,9 +44381,9 @@
     method public int getCid();
     method public int getLac();
     method @Deprecated public int getMcc();
-    method public String getMccString();
+    method @Nullable public String getMccString();
     method @Deprecated public int getMnc();
-    method public String getMncString();
+    method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method @Deprecated public int getPsc();
     method public void writeToParcel(android.os.Parcel, int);
@@ -44395,9 +44395,9 @@
     method public int getCi();
     method public int getEarfcn();
     method @Deprecated public int getMcc();
-    method public String getMccString();
+    method @Nullable public String getMccString();
     method @Deprecated public int getMnc();
-    method public String getMncString();
+    method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method public int getPci();
     method public int getTac();
@@ -44420,8 +44420,8 @@
     method public int getCid();
     method public int getCpid();
     method public int getLac();
-    method public String getMccString();
-    method public String getMncString();
+    method @Nullable public String getMccString();
+    method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method public int getUarfcn();
     method public void writeToParcel(android.os.Parcel, int);
@@ -44432,9 +44432,9 @@
     method public int getCid();
     method public int getLac();
     method @Deprecated public int getMcc();
-    method public String getMccString();
+    method @Nullable public String getMccString();
     method @Deprecated public int getMnc();
-    method public String getMncString();
+    method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method public int getPsc();
     method public int getUarfcn();
@@ -44457,22 +44457,22 @@
   }
 
   public final class CellInfoCdma extends android.telephony.CellInfo implements android.os.Parcelable {
-    method public android.telephony.CellIdentityCdma getCellIdentity();
-    method public android.telephony.CellSignalStrengthCdma getCellSignalStrength();
+    method @NonNull public android.telephony.CellIdentityCdma getCellIdentity();
+    method @NonNull public android.telephony.CellSignalStrengthCdma getCellSignalStrength();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellInfoCdma> CREATOR;
   }
 
   public final class CellInfoGsm extends android.telephony.CellInfo implements android.os.Parcelable {
-    method public android.telephony.CellIdentityGsm getCellIdentity();
-    method public android.telephony.CellSignalStrengthGsm getCellSignalStrength();
+    method @NonNull public android.telephony.CellIdentityGsm getCellIdentity();
+    method @NonNull public android.telephony.CellSignalStrengthGsm getCellSignalStrength();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellInfoGsm> CREATOR;
   }
 
   public final class CellInfoLte extends android.telephony.CellInfo implements android.os.Parcelable {
-    method public android.telephony.CellIdentityLte getCellIdentity();
-    method public android.telephony.CellSignalStrengthLte getCellSignalStrength();
+    method @NonNull public android.telephony.CellIdentityLte getCellIdentity();
+    method @NonNull public android.telephony.CellSignalStrengthLte getCellSignalStrength();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellInfoLte> CREATOR;
   }
@@ -44508,7 +44508,7 @@
     method public abstract boolean equals(Object);
     method public abstract int getAsuLevel();
     method public abstract int getDbm();
-    method public abstract int getLevel();
+    method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public abstract int getLevel();
     method public abstract int hashCode();
     field public static final int SIGNAL_STRENGTH_GOOD = 3; // 0x3
     field public static final int SIGNAL_STRENGTH_GREAT = 4; // 0x4
@@ -44528,7 +44528,7 @@
     method public int getEvdoEcio();
     method public int getEvdoLevel();
     method public int getEvdoSnr();
-    method public int getLevel();
+    method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthCdma> CREATOR;
   }
@@ -44538,7 +44538,7 @@
     method public int getAsuLevel();
     method public int getBitErrorRate();
     method public int getDbm();
-    method public int getLevel();
+    method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
     method public int getTimingAdvance();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR;
@@ -44549,7 +44549,7 @@
     method public int getAsuLevel();
     method public int getCqi();
     method public int getDbm();
-    method public int getLevel();
+    method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
     method public int getRsrp();
     method public int getRsrq();
     method public int getRssi();
@@ -44566,7 +44566,7 @@
     method public int getCsiRsrq();
     method public int getCsiSinr();
     method public int getDbm();
-    method public int getLevel();
+    method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
     method public int getSsRsrp();
     method public int getSsRsrq();
     method public int getSsSinr();
@@ -44578,7 +44578,7 @@
     method public int describeContents();
     method public int getAsuLevel();
     method public int getDbm();
-    method public int getLevel();
+    method @IntRange(from=0, to=4) public int getLevel();
     method public int getRscp();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthTdscdma> CREATOR;
@@ -44588,7 +44588,7 @@
     method public int describeContents();
     method public int getAsuLevel();
     method public int getDbm();
-    method public int getLevel();
+    method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
   }
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index 597b34bf..956161a 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -41,9 +41,11 @@
      * with remote animation targets should be relinquished. If {@param moveHomeToTop} is true, then
      * the home activity should be moved to the top. Otherwise, the home activity is hidden and the
      * user is returned to the app.
+     * @param sendUserLeaveHint If set to true, {@link Activity#onUserLeaving} will be sent to the
+     *                          top resumed app, false otherwise.
      */
     @UnsupportedAppUsage
-    void finish(boolean moveHomeToTop);
+    void finish(boolean moveHomeToTop, boolean sendUserLeaveHint);
 
     /**
      * Called by the handler to indicate that the recents animation input consumer should be
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index d4d0519..e02709e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -83,17 +83,18 @@
  * as needed.
  */
 public class ApplicationsState {
-    static final String TAG = "ApplicationsState";
-    static final boolean DEBUG = false;
-    static final boolean DEBUG_LOCKING = false;
+    private static final String TAG = "ApplicationsState";
 
     public static final int SIZE_UNKNOWN = -1;
     public static final int SIZE_INVALID = -2;
 
-    static final Pattern REMOVE_DIACRITICALS_PATTERN
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_LOCKING = false;
+    private static final Object sLock = new Object();
+    private static final Pattern REMOVE_DIACRITICALS_PATTERN
             = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
 
-    static final Object sLock = new Object();
+    @VisibleForTesting
     static ApplicationsState sInstance;
 
     public static ApplicationsState getInstance(Application app) {
@@ -126,13 +127,12 @@
 
     // Information about all applications.  Synchronize on mEntriesMap
     // to protect access to these.
-    final ArrayList<Session> mSessions = new ArrayList<Session>();
-    final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>();
+    final ArrayList<Session> mSessions = new ArrayList<>();
+    final ArrayList<Session> mRebuildingSessions = new ArrayList<>();
     private InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
     // Map: userid => (Map: package name => AppEntry)
-    final SparseArray<HashMap<String, AppEntry>> mEntriesMap =
-            new SparseArray<HashMap<String, AppEntry>>();
-    final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>();
+    final SparseArray<HashMap<String, AppEntry>> mEntriesMap = new SparseArray<>();
+    final ArrayList<AppEntry> mAppEntries = new ArrayList<>();
     List<ApplicationInfo> mApplications = new ArrayList<>();
     long mCurId = 1;
     UUID mCurComputingSizeUuid;
@@ -182,9 +182,10 @@
         mInterestingConfigChanges = interestingConfigChanges;
     }
 
-    public static final @SessionFlags int DEFAULT_SESSION_FLAGS =
+    @SessionFlags
+    public static final int DEFAULT_SESSION_FLAGS =
             FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS |
-            FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER;
+                    FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER;
 
     private ApplicationsState(Application app, IPackageManager iPackageManager) {
         mContext = app;
@@ -194,7 +195,7 @@
         mUm = mContext.getSystemService(UserManager.class);
         mStats = mContext.getSystemService(StorageStatsManager.class);
         for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
-            mEntriesMap.put(userId, new HashMap<String, AppEntry>());
+            mEntriesMap.put(userId, new HashMap<>());
         }
 
         mThread = new HandlerThread("ApplicationsState.Loader",
@@ -683,9 +684,16 @@
     private AppEntry getEntryLocked(ApplicationInfo info) {
         int userId = UserHandle.getUserId(info.uid);
         AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
-        if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
+        if (DEBUG) {
+            Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
+        }
         if (entry == null) {
-            if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName);
+            if (mHiddenModules.contains(info.packageName)) {
+                return null;
+            }
+            if (DEBUG) {
+                Log.i(TAG, "Creating AppEntry for " + info.packageName);
+            }
             entry = new AppEntry(mContext, info, mCurId++);
             mEntriesMap.get(userId).put(info.packageName, entry);
             mAppEntries.add(entry);
@@ -759,7 +767,8 @@
         boolean mRebuildForeground;
 
         private final boolean mHasLifecycle;
-        @SessionFlags private int mFlags = DEFAULT_SESSION_FLAGS;
+        @SessionFlags
+        private int mFlags = DEFAULT_SESSION_FLAGS;
 
         Session(Callbacks callbacks, Lifecycle lifecycle) {
             mCallbacks = callbacks;
@@ -771,7 +780,8 @@
             }
         }
 
-        public @SessionFlags int getSessionFlags() {
+        @SessionFlags
+        public int getSessionFlags() {
             return mFlags;
         }
 
@@ -863,25 +873,32 @@
                 filter.init(mContext);
             }
 
-            List<AppEntry> apps;
+            final List<AppEntry> apps;
             synchronized (mEntriesMap) {
                 apps = new ArrayList<>(mAppEntries);
             }
 
-            ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
-            if (DEBUG) Log.i(TAG, "Rebuilding...");
-            for (int i = 0; i < apps.size(); i++) {
-                AppEntry entry = apps.get(i);
+            ArrayList<AppEntry> filteredApps = new ArrayList<>();
+            if (DEBUG) {
+                Log.i(TAG, "Rebuilding...");
+            }
+            for (AppEntry entry : apps) {
                 if (entry != null && (filter == null || filter.filterApp(entry))) {
                     synchronized (mEntriesMap) {
-                        if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
+                        if (DEBUG_LOCKING) {
+                            Log.v(TAG, "rebuild acquired lock");
+                        }
                         if (comparator != null) {
                             // Only need the label if we are going to be sorting.
                             entry.ensureLabel(mContext);
                         }
-                        if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
+                        if (DEBUG) {
+                            Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
+                        }
                         filteredApps.add(entry);
-                        if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
+                        if (DEBUG_LOCKING) {
+                            Log.v(TAG, "rebuild releasing lock");
+                        }
                     }
                 }
             }
@@ -1290,7 +1307,8 @@
             }
         }
 
-        private @SessionFlags int getCombinedSessionFlags(List<Session> sessions) {
+        @SessionFlags
+        private int getCombinedSessionFlags(List<Session> sessions) {
             synchronized (mEntriesMap) {
                 int flags = 0;
                 for (Session session : sessions) {
@@ -1601,7 +1619,7 @@
             }
             if (object1.info != null && object2.info != null) {
                 compareResult =
-                    sCollator.compare(object1.info.packageName, object2.info.packageName);
+                        sCollator.compare(object1.info.packageName, object2.info.packageName);
                 if (compareResult != 0) {
                     return compareResult;
                 }
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_back.xml b/packages/SystemUI/res/drawable/ic_sysbar_back.xml
index 1448843..ee40262 100644
--- a/packages/SystemUI/res/drawable/ic_sysbar_back.xml
+++ b/packages/SystemUI/res/drawable/ic_sysbar_back.xml
@@ -17,6 +17,7 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="28dp"
     android:height="28dp"
+    android:autoMirrored="true"
     android:viewportWidth="28"
     android:viewportHeight="28">
 
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_back_quick_step.xml b/packages/SystemUI/res/drawable/ic_sysbar_back_quick_step.xml
index 93b2f9c8..442fafc 100644
--- a/packages/SystemUI/res/drawable/ic_sysbar_back_quick_step.xml
+++ b/packages/SystemUI/res/drawable/ic_sysbar_back_quick_step.xml
@@ -17,6 +17,7 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="28dp"
     android:height="28dp"
+    android:autoMirrored="true"
     android:viewportWidth="28"
     android:viewportHeight="28">
     <path
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 1d9105c..d2fe5cd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -77,9 +77,15 @@
         }
     }
 
-    public void finish(boolean toHome) {
+    /**
+     * Finish the current recents animation.
+     * @param toHome Going to home or back to the previous app.
+     * @param sendUserLeaveHint determines whether userLeaveHint will be set true to the previous
+     *                          app.
+     */
+    public void finish(boolean toHome, boolean sendUserLeaveHint) {
         try {
-            mAnimationController.finish(toHome);
+            mAnimationController.finish(toHome, sendUserLeaveHint);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to finish recents animation", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 34f3c60..33a2acf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -64,6 +64,7 @@
 import com.android.systemui.R;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.io.PrintWriter;
@@ -339,7 +340,7 @@
     }
 
     public void onConnectedToLauncher() {
-        if (!ONBOARDING_ENABLED) {
+        if (!ONBOARDING_ENABLED || QuickStepContract.isGesturalMode(mContext)) {
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 1d87a8b..443cc43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -98,8 +98,10 @@
         // Short-circuiting from UserManager. Needs to be extracted because of SystemUI boolean flag
         // qs_show_user_switcher_for_single_user
 
+        // The default in UserManager is to show the switcher. We want to not show it unless the
+        // user explicitly requests it in Settings
         final boolean userSwitcherEnabled = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.USER_SWITCHER_ENABLED, 1) != 0;
+                Settings.Global.USER_SWITCHER_ENABLED, 0) != 0;
 
         if (!UserManager.supportsMultipleUsers()
                 || mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index e2a63cc..544bb95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -507,9 +507,7 @@
         final boolean useAltBack =
                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
         final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-        float degrees = useAltBack
-                ? (isRtl ? 270 : -90)
-                : (isRtl ? 180 : 0);
+        float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
         if (drawable.getRotation() == degrees) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index 03c89c6..dd0c344 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -37,6 +37,7 @@
 import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
 import android.view.ContextThemeWrapper;
+import android.view.View;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -79,9 +80,10 @@
     private final ShadowDrawableState mState;
     private AnimatedVectorDrawable mAnimatedDrawable;
 
-    public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor) {
+    public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor,
+            boolean horizontalFlip) {
         this(d, new ShadowDrawableState(lightColor, darkColor,
-                d instanceof AnimatedVectorDrawable));
+                d instanceof AnimatedVectorDrawable, horizontalFlip));
     }
 
     private KeyButtonDrawable(Drawable d, ShadowDrawableState state) {
@@ -282,7 +284,12 @@
         // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
         final Drawable d = mState.mChildState.newDrawable().mutate();
         setDrawableBounds(d);
+        canvas.save();
+        if (mState.mHorizontalFlip) {
+            canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f);
+        }
         d.draw(canvas);
+        canvas.restore();
 
         if (mState.mIsHardwareBitmap) {
             bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
@@ -305,7 +312,12 @@
         // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
         final Drawable d = mState.mChildState.newDrawable().mutate();
         setDrawableBounds(d);
+        canvas.save();
+        if (mState.mHorizontalFlip) {
+            canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f);
+        }
         d.draw(canvas);
+        canvas.restore();
 
         // Draws the shadow from original drawable
         Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
@@ -357,6 +369,7 @@
         int mShadowColor;
         float mDarkIntensity;
         int mAlpha;
+        boolean mHorizontalFlip;
 
         boolean mIsHardwareBitmap;
         Bitmap mLastDrawnIcon;
@@ -368,11 +381,12 @@
         final boolean mSupportsAnimation;
 
         public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor,
-                boolean animated) {
+                boolean animated, boolean horizontalFlip) {
             mLightColor = lightColor;
             mDarkColor = darkColor;
             mSupportsAnimation = animated;
             mAlpha = 255;
+            mHorizontalFlip = horizontalFlip;
         }
 
         @Override
@@ -400,7 +414,7 @@
      * @return KeyButtonDrawable
      */
     public static KeyButtonDrawable create(@NonNull Context ctx, @DrawableRes int icon,
-        boolean hasShadow) {
+            boolean hasShadow) {
         final int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
         final int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
         Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
@@ -409,7 +423,7 @@
     }
 
     public static KeyButtonDrawable create(Context lightContext, Context darkContext,
-        @DrawableRes int iconResId, boolean hasShadow) {
+            @DrawableRes int iconResId, boolean hasShadow) {
         return create(lightContext,
             Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor),
             Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor),
@@ -418,10 +432,12 @@
 
     public static KeyButtonDrawable create(Context context, @ColorInt int lightColor,
         @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow) {
-        final KeyButtonDrawable drawable = new KeyButtonDrawable(context.getDrawable(iconResId),
-            lightColor, darkColor);
+        final Resources res = context.getResources();
+        boolean isRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+        Drawable d = context.getDrawable(iconResId);
+        final KeyButtonDrawable drawable = new KeyButtonDrawable(d, lightColor, darkColor,
+                isRtl && d.isAutoMirrored());
         if (hasShadow) {
-            final Resources res = context.getResources();
             int offsetX = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_x);
             int offsetY = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_y);
             int radius = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_radius);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
index e3c081e..c837c9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
@@ -179,8 +179,10 @@
         final int unusedColor = 0;
         final Drawable d = mock(Drawable.class);
         final ContextualButton button = spy(mBtn0);
-        final KeyButtonDrawable kbd1 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor));
-        final KeyButtonDrawable kbd2 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor));
+        final KeyButtonDrawable kbd1 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor,
+                false /* horizontalFlip */));
+        final KeyButtonDrawable kbd2 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor,
+                false /* horizontalFlip */));
         kbd1.setDarkIntensity(TEST_DARK_INTENSITY);
         kbd2.setDarkIntensity(0f);
 
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index f9980be..c1d872f 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -73,6 +73,13 @@
     /** Schedule a PiP mode changed callback when this animation ends. */
     public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2;
 
+    public static final int BOUNDS = 0;
+    public static final int FADE_IN = 1;
+
+    @IntDef({BOUNDS, FADE_IN}) public @interface AnimationType {}
+
+    private static final int FADE_IN_DURATION = 500;
+
     // Only accessed on UI thread.
     private ArrayMap<BoundsAnimationTarget, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
 
@@ -115,6 +122,7 @@
     private boolean mFinishAnimationAfterTransition = false;
     private final AnimationHandler mAnimationHandler;
     private Choreographer mChoreographer;
+    private @AnimationType int mAnimationType;
 
     private static final int WAIT_FOR_DRAW_TIMEOUT_MS = 3000;
 
@@ -140,6 +148,7 @@
             implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
 
         private final BoundsAnimationTarget mTarget;
+        private final @AnimationType int mAnimationType;
         private final Rect mFrom = new Rect();
         private final Rect mTo = new Rect();
         private final Rect mTmpRect = new Rect();
@@ -166,8 +175,8 @@
 
         // Depending on whether we are animating from
         // a smaller to a larger size
-        private final int mFrozenTaskWidth;
-        private final int mFrozenTaskHeight;
+        private int mFrozenTaskWidth;
+        private int mFrozenTaskHeight;
 
         // Timeout callback to ensure we continue the animation if waiting for resuming or app
         // windows drawn fails
@@ -176,12 +185,13 @@
             resume();
         };
 
-        BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
-                @SchedulePipModeChangedState int schedulePipModeChangedState,
+        BoundsAnimator(BoundsAnimationTarget target, @AnimationType int animationType, Rect from,
+                Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState,
                 @SchedulePipModeChangedState int prevShedulePipModeChangedState,
                 boolean moveFromFullscreen, boolean moveToFullscreen, Rect frozenTask) {
             super();
             mTarget = target;
+            mAnimationType = animationType;
             mFrom.set(from);
             mTo.set(to);
             mSchedulePipModeChangedState = schedulePipModeChangedState;
@@ -195,12 +205,14 @@
             // to their final size immediately so we can use scaling to make the window
             // larger. Likewise if we are going from bigger to smaller, we want to wait until
             // the end so we don't have to upscale from the smaller finished size.
-            if (animatingToLargerSize()) {
-                mFrozenTaskWidth = mTo.width();
-                mFrozenTaskHeight = mTo.height();
-            } else {
-                mFrozenTaskWidth = frozenTask.isEmpty() ? mFrom.width() : frozenTask.width();
-                mFrozenTaskHeight = frozenTask.isEmpty() ? mFrom.height() : frozenTask.height();
+            if (mAnimationType == BOUNDS) {
+                if (animatingToLargerSize()) {
+                    mFrozenTaskWidth = mTo.width();
+                    mFrozenTaskHeight = mTo.height();
+                } else {
+                    mFrozenTaskWidth = frozenTask.isEmpty() ? mFrom.width() : frozenTask.width();
+                    mFrozenTaskHeight = frozenTask.isEmpty() ? mFrom.height() : frozenTask.height();
+                }
             }
         }
 
@@ -222,8 +234,9 @@
             // otherwise.
             boolean continueAnimation;
             if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) {
-                continueAnimation = mTarget.onAnimationStart(mSchedulePipModeChangedState ==
-                        SCHEDULE_PIP_MODE_CHANGED_ON_START, false /* forceUpdate */);
+                continueAnimation = mTarget.onAnimationStart(
+                        mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START,
+                        false /* forceUpdate */, mAnimationType);
 
                 // When starting an animation from fullscreen, pause here and wait for the
                 // windows-drawn signal before we start the rest of the transition down into PiP.
@@ -238,7 +251,8 @@
                 // However, we still need to report to them that they are leaving PiP, so this will
                 // force an update via a mode changed callback.
                 continueAnimation = mTarget.onAnimationStart(
-                        true /* schedulePipModeChangedCallback */, true /* forceUpdate */);
+                        true /* schedulePipModeChangedCallback */, true /* forceUpdate */,
+                        mAnimationType);
             } else {
                 // The animation is already running, but we should check that the TaskStack is still
                 // valid before continuing with the animation
@@ -285,6 +299,13 @@
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
             final float value = (Float) animation.getAnimatedValue();
+            if (mAnimationType == FADE_IN) {
+                if (!mTarget.setPinnedStackAlpha(value)) {
+                    cancelAndCallAnimationEnd();
+                }
+                return;
+            }
+
             final float remains = 1 - value;
             mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
             mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
@@ -408,16 +429,29 @@
 
     public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to,
             int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
-            boolean moveFromFullscreen, boolean moveToFullscreen) {
+            boolean moveFromFullscreen, boolean moveToFullscreen,
+            @AnimationType int animationType) {
         animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState,
-                moveFromFullscreen, moveToFullscreen);
+                moveFromFullscreen, moveToFullscreen, animationType);
     }
 
     @VisibleForTesting
     BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to,
             int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
-            boolean moveFromFullscreen, boolean moveToFullscreen) {
+            boolean moveFromFullscreen, boolean moveToFullscreen,
+            @AnimationType int animationType) {
         final BoundsAnimator existing = mRunningAnimations.get(target);
+        // animateBoundsImpl gets called twice for each animation. The second time we get the final
+        // to rect that respects the shelf, which is when we want to resize. Our signal for fade in
+        // comes in from how to enter into pip, but we also need to use the to and from rect to
+        // decide which animation we want to run finally.
+        boolean shouldResize = false;
+        if (isRunningFadeInAnimation(target)) {
+            shouldResize = true;
+            if (from.contains(to)) {
+                animationType = FADE_IN;
+            }
+        }
         final boolean replacing = existing != null;
         @SchedulePipModeChangedState int prevSchedulePipModeChangedState =
                 NO_PIP_MODE_CHANGED_CALLBACKS;
@@ -477,18 +511,34 @@
             // Since we are replacing, we skip both animation start and end callbacks
             existing.cancel();
         }
-        final BoundsAnimator animator = new BoundsAnimator(target, from, to,
+        if (shouldResize) {
+            target.setPinnedStackSize(to, null);
+        }
+        final BoundsAnimator animator = new BoundsAnimator(target, animationType, from, to,
                 schedulePipModeChangedState, prevSchedulePipModeChangedState,
                 moveFromFullscreen, moveToFullscreen, frozenTask);
         mRunningAnimations.put(target, animator);
         animator.setFloatValues(0f, 1f);
-        animator.setDuration((animationDuration != -1 ? animationDuration
-                : DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
+        animator.setDuration(animationType == FADE_IN ? FADE_IN_DURATION
+                : (animationDuration != -1 ? animationDuration : DEFAULT_TRANSITION_DURATION)
+                        * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
         animator.setInterpolator(mFastOutSlowInInterpolator);
         animator.start();
         return animator;
     }
 
+    public void setAnimationType(@AnimationType int animationType) {
+        mAnimationType = animationType;
+    }
+
+    /** return the current animation type. */
+    public @AnimationType int getAnimationType() {
+        @AnimationType int animationType = mAnimationType;
+        // Default to BOUNDS.
+        mAnimationType = BOUNDS;
+        return animationType;
+    }
+
     public Handler getHandler() {
         return mHandler;
     }
@@ -498,6 +548,11 @@
         mHandler.post(this::resume);
     }
 
+    private boolean isRunningFadeInAnimation(final BoundsAnimationTarget target) {
+        final BoundsAnimator existing = mRunningAnimations.get(target);
+        return existing != null && existing.mAnimationType == FADE_IN && existing.isStarted();
+    }
+
     private void resume() {
         for (int i = 0; i < mRunningAnimations.size(); i++) {
             final BoundsAnimator b = mRunningAnimations.valueAt(i);
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
index 5cb80de..9f54e49e0 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
@@ -32,7 +32,8 @@
      * callbacks
      * @return whether to continue the animation
      */
-    boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate);
+    boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate,
+            @BoundsAnimationController.AnimationType int animationType);
 
     /**
      * @return Whether the animation should be paused waiting for the windows to draw before
@@ -53,6 +54,9 @@
      */
     boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds);
 
+    /** Sets the alpha of the animation target */
+    boolean setPinnedStackAlpha(float alpha);
+
     /**
      * Callback for the target to inform it that the animation has ended, so it can do some
      * necessary cleanup.
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 144efb4..07d3fb9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -26,6 +26,8 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.BoundsAnimationController.BOUNDS;
+import static com.android.server.wm.BoundsAnimationController.FADE_IN;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
@@ -201,7 +203,8 @@
         }
     }
 
-    private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
+    private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode,
+            boolean sendUserLeaveHint) {
         synchronized (mService.mGlobalLock) {
             if (DEBUG) Slog.d(TAG, "onAnimationFinished(): controller="
                     + mWindowManager.getRecentsAnimationController()
@@ -246,7 +249,18 @@
                     if (reorderMode == REORDER_MOVE_TO_TOP) {
                         // Bring the target stack to the front
                         mStackSupervisor.mNoAnimActivities.add(targetActivity);
-                        targetStack.moveToFront("RecentsAnimation.onAnimationFinished()");
+
+                        if (sendUserLeaveHint) {
+                            // Setting this allows the previous app to PiP.
+                            mStackSupervisor.mUserLeaving = true;
+                            targetStack.moveTaskToFrontLocked(targetActivity.getTaskRecord(),
+                                    true /* noAnimation */, null /* activityOptions */,
+                                    targetActivity.appTimeTracker,
+                                    "RecentsAnimation.onAnimationFinished()");
+                        } else {
+                            targetStack.moveToFront("RecentsAnimation.onAnimationFinished()");
+                        }
+
                         if (DEBUG) {
                             final ActivityStack topStack = getTopNonAlwaysOnTopStack();
                             if (topStack != targetStack) {
@@ -300,11 +314,11 @@
 
     @Override
     public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
-            boolean runSychronously) {
+            boolean runSychronously, boolean sendUserLeaveHint) {
         if (runSychronously) {
-            finishAnimation(reorderMode);
+            finishAnimation(reorderMode, sendUserLeaveHint);
         } else {
-            mService.mH.post(() -> finishAnimation(reorderMode));
+            mService.mH.post(() -> finishAnimation(reorderMode, sendUserLeaveHint));
         }
     }
 
@@ -317,6 +331,10 @@
         }
         final RecentsAnimationController controller =
                 mWindowManager.getRecentsAnimationController();
+        final DisplayContent dc =
+                mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent;
+        dc.mBoundsAnimationController.setAnimationType(
+                controller.shouldCancelWithDeferredScreenshot() ? FADE_IN : BOUNDS);
 
         // Cancel running recents animation and screenshot previous task when the next
         // transition starts in below cases:
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 3813669..d2c510f 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -27,6 +27,7 @@
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
+import static com.android.server.wm.BoundsAnimationController.FADE_IN;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
@@ -142,7 +143,9 @@
     };
 
     public interface RecentsAnimationCallbacks {
-        void onAnimationFinished(@ReorderMode int reorderMode, boolean runSychronously);
+        /** Callback when recents animation is finished. */
+        void onAnimationFinished(@ReorderMode int reorderMode, boolean runSychronously,
+                boolean sendUserLeaveHint);
     }
 
     private final IRecentsAnimationController mController =
@@ -179,7 +182,7 @@
         }
 
         @Override
-        public void finish(boolean moveHomeToTop) {
+        public void finish(boolean moveHomeToTop, boolean sendUserLeaveHint) {
             if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "finish(" + moveHomeToTop + "):"
                     + " mCanceled=" + mCanceled);
             final long token = Binder.clearCallingIdentity();
@@ -195,7 +198,9 @@
                 mCallbacks.onAnimationFinished(moveHomeToTop
                         ? REORDER_MOVE_TO_TOP
                         : REORDER_MOVE_TO_ORIGINAL_POSITION,
-                        true /* runSynchronously */);
+                        true /* runSynchronously */, sendUserLeaveHint);
+                final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId);
+                dc.mBoundsAnimationController.setAnimationType(FADE_IN);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -497,7 +502,8 @@
                 Slog.e(TAG, "Failed to cancel recents animation", e);
             }
             // Clean up and return to the previous app
-            mCallbacks.onAnimationFinished(reorderMode, runSynchronously);
+            mCallbacks.onAnimationFinished(reorderMode, runSynchronously,
+                    false /* sendUserLeaveHint */);
         }
     }
 
@@ -542,7 +548,8 @@
                         if (DEBUG_RECENTS_ANIMATIONS) {
                             Slog.d(TAG, "mRecentScreenshotAnimator finish");
                         }
-                        mCallbacks.onAnimationFinished(reorderMode, runSynchronously);
+                        mCallbacks.onAnimationFinished(reorderMode, runSynchronously,
+                                false /* sendUserLeaveHint */);
                     }, mService);
             mRecentScreenshotAnimator.transferAnimation(task.mSurfaceAnimator);
         }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 09baf8c..bdb4d04 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -34,6 +34,7 @@
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
 
+import static com.android.server.wm.BoundsAnimationController.FADE_IN;
 import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
@@ -148,6 +149,7 @@
     private boolean mCancelCurrentBoundsAnimation = false;
     private Rect mBoundsAnimationTarget = new Rect();
     private Rect mBoundsAnimationSourceHintBounds = new Rect();
+    private @BoundsAnimationController.AnimationType int mAnimationType;
 
     Rect mPreAnimationBounds = new Rect();
 
@@ -1572,7 +1574,8 @@
     }
 
     @Override  // AnimatesBounds
-    public boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
+    public boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate,
+            @BoundsAnimationController.AnimationType int animationType) {
         // Hold the lock since this is called from the BoundsAnimator running on the UiThread
         synchronized (mWmService.mGlobalLock) {
             if (!isAttached()) {
@@ -1583,6 +1586,7 @@
             mBoundsAnimatingRequested = false;
             mBoundsAnimating = true;
             mCancelCurrentBoundsAnimation = false;
+            mAnimationType = animationType;
 
             // If we are changing UI mode, as in the PiP to fullscreen
             // transition, then we need to wait for the window to draw.
@@ -1599,7 +1603,8 @@
                 // I don't believe you...
             }
 
-            if (schedulePipModeChangedCallback && mActivityStack != null) {
+            if ((schedulePipModeChangedCallback || animationType == FADE_IN)
+                    && mActivityStack != null) {
                 // We need to schedule the PiP mode change before the animation up. It is possible
                 // in this case for the animation down to not have been completed, so always
                 // force-schedule and update to the client to ensure that it is notified that it
@@ -1614,6 +1619,21 @@
     @Override  // AnimatesBounds
     public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize,
             boolean moveToFullscreen) {
+        if (mAnimationType == BoundsAnimationController.FADE_IN) {
+            setPinnedStackAlpha(1f);
+            try {
+                mWmService.mActivityTaskManager.notifyPinnedStackAnimationEnded();
+            } catch (RemoteException e) {
+                // I don't believe you...
+            }
+            return;
+        }
+
+        onBoundAnimationEnd(schedulePipModeChangedCallback, finalStackSize, moveToFullscreen);
+    }
+
+    private void onBoundAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize,
+            boolean moveToFullscreen) {
         if (inPinnedWindowingMode()) {
             // Update to the final bounds if requested. This is done here instead of in the bounds
             // animator to allow us to coordinate this after we notify the PiP mode changed
@@ -1725,10 +1745,23 @@
         final @SchedulePipModeChangedState int finalSchedulePipModeChangedState =
                 schedulePipModeChangedState;
         final DisplayContent displayContent = getDisplayContent();
+        @BoundsAnimationController.AnimationType int intendedAnimationType =
+                displayContent.mBoundsAnimationController.getAnimationType();
+        if (intendedAnimationType == FADE_IN) {
+            if (fromFullscreen) {
+                setPinnedStackAlpha(0f);
+            }
+            if (toBounds.width() == fromBounds.width()
+                    && toBounds.height() == fromBounds.height()) {
+                intendedAnimationType = BoundsAnimationController.BOUNDS;
+            }
+        }
+
+        final @BoundsAnimationController.AnimationType int animationType = intendedAnimationType;
         displayContent.mBoundsAnimationController.getHandler().post(() -> {
             displayContent.mBoundsAnimationController.animateBounds(this, fromBounds,
                     finalToBounds, animationDuration, finalSchedulePipModeChangedState,
-                    fromFullscreen, toFullscreen);
+                    fromFullscreen, toFullscreen, animationType);
         });
     }
 
@@ -1905,6 +1938,20 @@
         }
     }
 
+    @Override
+    public boolean setPinnedStackAlpha(float alpha) {
+        // Hold the lock since this is called from the BoundsAnimator running on the UiThread
+        synchronized (mWmService.mGlobalLock) {
+            if (mCancelCurrentBoundsAnimation) {
+                return false;
+            }
+            getPendingTransaction().setAlpha(getSurfaceControl(), alpha);
+            scheduleAnimation();
+        }
+
+        return true;
+    }
+
     public DisplayInfo getDisplayInfo() {
         return mDisplayContent.getDisplayInfo();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 9ce5795..beec1a8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -18,6 +18,8 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.server.wm.BoundsAnimationController.BOUNDS;
+import static com.android.server.wm.BoundsAnimationController.FADE_IN;
 import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
@@ -131,6 +133,8 @@
         boolean mCancelRequested;
         Rect mStackBounds;
         Rect mTaskBounds;
+        float mAlpha;
+        @BoundsAnimationController.AnimationType int mAnimationType;
 
         void initialize(Rect from) {
             mAwaitingAnimationStart = true;
@@ -148,11 +152,12 @@
 
         @Override
         public boolean onAnimationStart(boolean schedulePipModeChangedCallback,
-                boolean forceUpdate) {
+                boolean forceUpdate, @BoundsAnimationController.AnimationType int animationType) {
             mAwaitingAnimationStart = false;
             mAnimationStarted = true;
             mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
             mForcePipModeChangedCallback = forceUpdate;
+            mAnimationType = animationType;
             return true;
         }
 
@@ -185,6 +190,12 @@
             mMovedToFullscreen = moveToFullscreen;
             mTaskBounds = null;
         }
+
+        @Override
+        public boolean setPinnedStackAlpha(float alpha) {
+            mAlpha = alpha;
+            return true;
+        }
     }
 
     /**
@@ -201,6 +212,7 @@
         private Rect mTo;
         private Rect mLargerBounds;
         private Rect mExpectedFinalBounds;
+        private @BoundsAnimationController.AnimationType int mAnimationType;
 
         BoundsAnimationDriver(BoundsAnimationController controller,
                 TestBoundsAnimationTarget target, MockValueAnimator mockValueAnimator) {
@@ -209,7 +221,8 @@
             mMockAnimator = mockValueAnimator;
         }
 
-        BoundsAnimationDriver start(Rect from, Rect to) {
+        BoundsAnimationDriver start(Rect from, Rect to,
+                @BoundsAnimationController.AnimationType int animationType) {
             if (mAnimator != null) {
                 throw new IllegalArgumentException("Call restart() to restart an animation");
             }
@@ -223,7 +236,7 @@
             assertTrue(mTarget.mAwaitingAnimationStart);
             assertFalse(mTarget.mAnimationStarted);
 
-            startImpl(from, to);
+            startImpl(from, to, animationType);
 
             // Ensure that the animator is paused for the all windows drawn signal when animating
             // to/from fullscreen
@@ -253,7 +266,7 @@
             mTarget.mAnimationStarted = false;
 
             // Start animation
-            startImpl(mTarget.mStackBounds, to);
+            startImpl(mTarget.mStackBounds, to, BOUNDS);
 
             if (toSameBounds) {
                 // Same animator if same final bounds
@@ -273,13 +286,15 @@
             return this;
         }
 
-        private BoundsAnimationDriver startImpl(Rect from, Rect to) {
+        private BoundsAnimationDriver startImpl(Rect from, Rect to,
+                @BoundsAnimationController.AnimationType int animationType) {
             boolean fromFullscreen = from.equals(BOUNDS_FULL);
             boolean toFullscreen = to.equals(BOUNDS_FULL);
             mFrom = new Rect(from);
             mTo = new Rect(to);
             mExpectedFinalBounds = new Rect(to);
             mLargerBounds = getLargerBounds(mFrom, mTo);
+            mAnimationType = animationType;
 
             // Start animation
             final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen
@@ -288,17 +303,19 @@
                             ? SCHEDULE_PIP_MODE_CHANGED_ON_END
                             : NO_PIP_MODE_CHANGED_CALLBACKS;
             mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION,
-                    schedulePipModeChangedState, fromFullscreen, toFullscreen);
+                    schedulePipModeChangedState, fromFullscreen, toFullscreen, animationType);
 
-            // Original stack bounds, frozen task bounds
-            assertEquals(mFrom, mTarget.mStackBounds);
-            assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
+            if (animationType == BOUNDS) {
+                // Original stack bounds, frozen task bounds
+                assertEquals(mFrom, mTarget.mStackBounds);
+                assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
 
-            // Animating to larger size
-            if (mFrom.equals(mLargerBounds)) {
-                assertFalse(mAnimator.animatingToLargerSize());
-            } else if (mTo.equals(mLargerBounds)) {
-                assertTrue(mAnimator.animatingToLargerSize());
+                // Animating to larger size
+                if (mFrom.equals(mLargerBounds)) {
+                    assertFalse(mAnimator.animatingToLargerSize());
+                } else if (mTo.equals(mLargerBounds)) {
+                    assertTrue(mAnimator.animatingToLargerSize());
+                }
             }
 
             return this;
@@ -315,16 +332,20 @@
         BoundsAnimationDriver update(float t) {
             mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(t));
 
-            // Temporary stack bounds, frozen task bounds
-            if (t == 0f) {
-                assertEquals(mFrom, mTarget.mStackBounds);
-            } else if (t == 1f) {
-                assertEquals(mTo, mTarget.mStackBounds);
+            if (mAnimationType == BOUNDS) {
+                // Temporary stack bounds, frozen task bounds
+                if (t == 0f) {
+                    assertEquals(mFrom, mTarget.mStackBounds);
+                } else if (t == 1f) {
+                    assertEquals(mTo, mTarget.mStackBounds);
+                } else {
+                    assertNotEquals(mFrom, mTarget.mStackBounds);
+                    assertNotEquals(mTo, mTarget.mStackBounds);
+                }
+                assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
             } else {
-                assertNotEquals(mFrom, mTarget.mStackBounds);
-                assertNotEquals(mTo, mTarget.mStackBounds);
+                assertEquals((float) mMockAnimator.getAnimatedValue(), mTarget.mAlpha, 0.01f);
             }
-            assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
             return this;
         }
 
@@ -353,10 +374,14 @@
         BoundsAnimationDriver end() {
             mAnimator.end();
 
-            // Final stack bounds
-            assertEquals(mTo, mTarget.mStackBounds);
-            assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds);
-            assertNull(mTarget.mTaskBounds);
+            if (mAnimationType == BOUNDS) {
+                // Final stack bounds
+                assertEquals(mTo, mTarget.mStackBounds);
+                assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds);
+                assertNull(mTarget.mTaskBounds);
+            } else {
+                assertEquals(mTarget.mAlpha, 1f, 0.01f);
+            }
 
             return this;
         }
@@ -413,7 +438,7 @@
     @UiThreadTest
     @Test
     public void testFullscreenToFloatingTransition() {
-        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0f)
                 .update(0.5f)
@@ -425,7 +450,7 @@
     @UiThreadTest
     @Test
     public void testFloatingToFullscreenTransition() {
-        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL, BOUNDS)
                 .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
                 .update(0f)
                 .update(0.5f)
@@ -437,7 +462,7 @@
     @UiThreadTest
     @Test
     public void testFloatingToSmallerFloatingTransition() {
-        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING)
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING, BOUNDS)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0f)
                 .update(0.5f)
@@ -449,7 +474,7 @@
     @UiThreadTest
     @Test
     public void testFloatingToLargerFloatingTransition() {
-        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
+        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING, BOUNDS)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0f)
                 .update(0.5f)
@@ -463,7 +488,7 @@
     @UiThreadTest
     @Test
     public void testFullscreenToFloatingCancelFromTarget() {
-        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
                 .cancel()
@@ -473,7 +498,7 @@
     @UiThreadTest
     @Test
     public void testFullscreenToFloatingCancelFromAnimationToSameBounds() {
-        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
                 .restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */)
@@ -484,7 +509,7 @@
     @UiThreadTest
     @Test
     public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() {
-        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
                 .restart(BOUNDS_SMALLER_FLOATING,
@@ -498,7 +523,7 @@
     public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() {
         // When animating from fullscreen and the animation is interruped, we expect the animation
         // start callback to be made, with a forced pip mode change callback
-        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
                 .restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */)
@@ -511,7 +536,7 @@
     @UiThreadTest
     @Test
     public void testFloatingToFullscreenCancelFromTarget() {
-        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL, BOUNDS)
                 .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
                 .cancel()
@@ -521,7 +546,7 @@
     @UiThreadTest
     @Test
     public void testFloatingToFullscreenCancelFromAnimationToSameBounds() {
-        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL, BOUNDS)
                 .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
                 .restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */)
@@ -532,7 +557,7 @@
     @UiThreadTest
     @Test
     public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() {
-        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL, BOUNDS)
                 .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
                 .restart(BOUNDS_SMALLER_FLOATING,
@@ -546,7 +571,7 @@
     @UiThreadTest
     @Test
     public void testFloatingToSmallerFloatingCancelFromTarget() {
-        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING)
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING, BOUNDS)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
                 .cancel()
@@ -556,13 +581,25 @@
     @UiThreadTest
     @Test
     public void testFloatingToLargerFloatingCancelFromTarget() {
-        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
+        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING, BOUNDS)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
                 .cancel()
                 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
     }
 
+    @UiThreadTest
+    @Test
+    public void testFadeIn() {
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, FADE_IN)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0f)
+                .update(0.5f)
+                .update(1f)
+                .end()
+                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
     /** MISC **/
 
     @UiThreadTest
@@ -570,7 +607,7 @@
     public void testBoundsAreCopied() {
         Rect from = new Rect(0, 0, 100, 100);
         Rect to = new Rect(25, 25, 75, 75);
-        mDriver.start(from, to)
+        mDriver.start(from, to, BOUNDS)
                 .update(0.25f)
                 .end();
         assertEquals(new Rect(0, 0, 100, 100), from);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index e392353..0c2ce61 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -163,7 +163,7 @@
         // Assume IRecentsAnimationController#cleanupScreenshot called to finish screenshot
         // animation.
         mController.mRecentScreenshotAnimator.cancelAnimation();
-        verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, true);
+        verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, true, false);
     }
 
     private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 5625ea4..f615823 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -71,6 +71,7 @@
     @Test
     public void testCancelAnimationOnVisibleStackOrderChange() {
         ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        display.mDisplayContent.mBoundsAnimationController = mock(BoundsAnimationController.class);
         ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         new ActivityBuilder(mService)
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0f8f873..970bbf8 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2803,6 +2803,19 @@
     public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL =
             "key_is_opportunistic_subscription_bool";
 
+    /**
+     * A list of 4 GSM RSSI thresholds above which a signal level is considered POOR,
+     * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
+     *
+     * Note that the min and max thresholds are fixed at -113 and -51, as set in 3GPP TS 27.007
+     * section 8.5.
+     * <p>
+     * See CellSignalStrengthGsm#GSM_RSSI_MAX and CellSignalStrengthGsm#GSM_RSSI_MIN. Any signal
+     * level outside these boundaries is considered invalid.
+     * @hide
+     */
+    public static final String KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY =
+            "gsm_rssi_thresholds_int_array";
 
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
@@ -3204,6 +3217,13 @@
                 false);
         sDefaults.putString(KEY_SUBSCRIPTION_GROUP_UUID_STRING, "");
         sDefaults.putBoolean(KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL, false);
+        sDefaults.putIntArray(KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -107, /* SIGNAL_STRENGTH_POOR */
+                        -103, /* SIGNAL_STRENGTH_MODERATE */
+                        -97, /* SIGNAL_STRENGTH_GOOD */
+                        -89,  /* SIGNAL_STRENGTH_GREAT */
+                });
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 5e44bf2..864540d 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -166,6 +166,7 @@
     /**
      * @return Mobile Country Code in string format, null if unavailable.
      */
+    @Nullable
     public String getMccString() {
         return mMccStr;
     }
@@ -173,6 +174,7 @@
     /**
      * @return Mobile Network Code in string format, null if unavailable.
      */
+    @Nullable
     public String getMncString() {
         return mMncStr;
     }
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 2dd72d6..14503c7 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -187,6 +187,7 @@
     /**
      * @return Mobile Country Code in string format, null if unavailable.
      */
+    @Nullable
     public String getMccString() {
         return mMccStr;
     }
@@ -194,6 +195,7 @@
     /**
      * @return Mobile Network Code in string format, null if unavailable.
      */
+    @Nullable
     public String getMncString() {
         return mMncStr;
     }
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index a591bd1..937de70 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -104,6 +104,7 @@
      * Get Mobile Country Code in string format
      * @return Mobile Country Code in string format, null if unknown
      */
+    @Nullable
     public String getMccString() {
         return mMccStr;
     }
@@ -112,6 +113,7 @@
      * Get Mobile Network Code in string format
      * @return Mobile Network Code in string format, null if unknown
      */
+    @Nullable
     public String getMncString() {
         return mMncStr;
     }
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 674c40c..b4a2ead 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -150,6 +150,7 @@
     /**
      * @return Mobile Country Code in string version, null if unavailable.
      */
+    @Nullable
     public String getMccString() {
         return mMccStr;
     }
@@ -157,6 +158,7 @@
     /**
      * @return Mobile Network Code in string version, null if unavailable.
      */
+    @Nullable
     public String getMncString() {
         return mMncStr;
     }
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index a4570e4..30b131f 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -76,18 +77,25 @@
                 new CellSignalStrengthCdma(cic.signalStrengthCdma, cic.signalStrengthEvdo);
     }
 
+    /**
+     * @return a {@link CellIdentityCdma} instance.
+     */
     @Override
-    public CellIdentityCdma getCellIdentity() {
+    public @NonNull CellIdentityCdma getCellIdentity() {
         return mCellIdentityCdma;
     }
+
     /** @hide */
     @UnsupportedAppUsage
     public void setCellIdentity(CellIdentityCdma cid) {
         mCellIdentityCdma = cid;
     }
 
+    /**
+     * @return a {@link CellSignalStrengthCdma} instance.
+     */
     @Override
-    public CellSignalStrengthCdma getCellSignalStrength() {
+    public @NonNull CellSignalStrengthCdma getCellSignalStrength() {
         return mCellSignalStrengthCdma;
     }
 
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index ce32bc1..137f97e 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -71,17 +72,24 @@
         mCellSignalStrengthGsm = new CellSignalStrengthGsm(cig.signalStrengthGsm);
     }
 
+    /**
+     * @return a {@link CellIdentityGsm} instance.
+     */
     @Override
-    public CellIdentityGsm getCellIdentity() {
+    public @NonNull CellIdentityGsm getCellIdentity() {
         return mCellIdentityGsm;
     }
+
     /** @hide */
     public void setCellIdentity(CellIdentityGsm cid) {
         mCellIdentityGsm = cid;
     }
 
+    /**
+     * @return a {@link CellSignalStrengthGsm} instance.
+     */
     @Override
-    public CellSignalStrengthGsm getCellSignalStrength() {
+    public @NonNull CellSignalStrengthGsm getCellSignalStrength() {
         return mCellSignalStrengthGsm;
     }
 
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 01ee20a..da7b7ab 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -79,11 +80,15 @@
         mCellConfig = new CellConfigLte(cil.cellConfig);
     }
 
+    /**
+     * @return a {@link CellIdentityLte} instance.
+     */
     @Override
-    public CellIdentityLte getCellIdentity() {
+    public @NonNull CellIdentityLte getCellIdentity() {
         if (DBG) log("getCellIdentity: " + mCellIdentityLte);
         return mCellIdentityLte;
     }
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public void setCellIdentity(CellIdentityLte cid) {
@@ -91,8 +96,11 @@
         mCellIdentityLte = cid;
     }
 
+    /**
+     * @return a {@link CellSignalStrengthLte} instance.
+     */
     @Override
-    public CellSignalStrengthLte getCellSignalStrength() {
+    public @NonNull CellSignalStrengthLte getCellSignalStrength() {
         if (DBG) log("getCellSignalStrength: " + mCellSignalStrengthLte);
         return mCellSignalStrengthLte;
     }
diff --git a/telephony/java/android/telephony/CellInfoNr.java b/telephony/java/android/telephony/CellInfoNr.java
index ba4a907..9775abd 100644
--- a/telephony/java/android/telephony/CellInfoNr.java
+++ b/telephony/java/android/telephony/CellInfoNr.java
@@ -43,12 +43,18 @@
         mCellSignalStrength = other.mCellSignalStrength;
     }
 
+    /**
+     * @return a {@link CellIdentityNr} instance.
+     */
     @Override
     @NonNull
     public CellIdentity getCellIdentity() {
         return mCellIdentity;
     }
 
+    /**
+     * @return a {@link CellSignalStrengthNr} instance.
+     */
     @Override
     @NonNull
     public CellSignalStrength getCellSignalStrength() {
diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java
index ccafda6..f1305f5 100644
--- a/telephony/java/android/telephony/CellInfoTdscdma.java
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -75,6 +75,9 @@
         mCellSignalStrengthTdscdma = new CellSignalStrengthTdscdma(cit.signalStrengthTdscdma);
     }
 
+    /**
+     * @return a {@link CellIdentityTdscdma} instance.
+     */
     @Override
     public @NonNull CellIdentityTdscdma getCellIdentity() {
         return mCellIdentityTdscdma;
@@ -85,6 +88,9 @@
         mCellIdentityTdscdma = cid;
     }
 
+    /**
+     * @return a {@link CellSignalStrengthTdscdma} instance.
+     */
     @Override
     public @NonNull CellSignalStrengthTdscdma getCellSignalStrength() {
         return mCellSignalStrengthTdscdma;
diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java
index 1b32178..ee5fec8 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -71,15 +71,22 @@
         mCellSignalStrengthWcdma = new CellSignalStrengthWcdma(ciw.signalStrengthWcdma);
     }
 
+    /**
+     * @return a {@link CellIdentityWcdma} instance.
+     */
     @Override
     public CellIdentityWcdma getCellIdentity() {
         return mCellIdentityWcdma;
     }
+
     /** @hide */
     public void setCellIdentity(CellIdentityWcdma cid) {
         mCellIdentityWcdma = cid;
     }
 
+    /**
+     * @return a {@link CellSignalStrengthWcdma} instance.
+     */
     @Override
     public CellSignalStrengthWcdma getCellSignalStrength() {
         return mCellSignalStrengthWcdma;
diff --git a/telephony/java/android/telephony/CellSignalStrength.java b/telephony/java/android/telephony/CellSignalStrength.java
index 740b970..e65b048ec 100644
--- a/telephony/java/android/telephony/CellSignalStrength.java
+++ b/telephony/java/android/telephony/CellSignalStrength.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.IntRange;
 import android.os.PersistableBundle;
 
 /**
@@ -57,23 +58,24 @@
     public abstract void setDefaultValues();
 
     /**
-     * Get signal level as an int from 0..4
-     * <p>
-     * @see #SIGNAL_STRENGTH_NONE_OR_UNKNOWN
-     * @see #SIGNAL_STRENGTH_POOR
-     * @see #SIGNAL_STRENGTH_MODERATE
-     * @see #SIGNAL_STRENGTH_GOOD
-     * @see #SIGNAL_STRENGTH_GREAT
+     * Retrieve an abstract level value for the overall signal quality.
+     *
+     * @return a single integer from 0 to 4 representing the general signal quality.
+     *     0 represents very poor or unknown signal quality while 4 represents excellent
+     *     signal quality.
      */
+    @IntRange(from = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to = SIGNAL_STRENGTH_GREAT)
     public abstract int getLevel();
 
     /**
-     * Get the signal level as an asu value between 0..31, 99 is unknown
+     * Get the technology-specific signal strength in Arbitrary Strength Units, calculated from the
+     * strength of the pilot signal or equivalent.
      */
     public abstract int getAsuLevel();
 
     /**
-     * Get the signal strength as dBm
+     * Get the technology-specific signal strength in dBm, which is the signal strength of the
+     * pilot signal or equivalent.
      */
     public abstract int getDbm();
 
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 5b19599..1998439 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.IntRange;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
@@ -114,13 +115,9 @@
         mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
     }
 
-    /**
-     * Retrieve an abstract level value for the overall signal strength.
-     *
-     * @return a single integer from 0 to 4 representing the general signal quality.
-     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
-     */
+    /** {@inheritDoc} */
     @Override
+    @IntRange(from = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to = SIGNAL_STRENGTH_GREAT)
     public int getLevel() {
         return mLevel;
     }
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 0aeb0f6..14ae689 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.IntRange;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -37,6 +38,10 @@
     private static final int GSM_RSSI_GOOD = -97;
     private static final int GSM_RSSI_MODERATE = -103;
     private static final int GSM_RSSI_POOR = -107;
+    private static final int GSM_RSSI_MIN = -113;
+
+    private static final int[] sRssiThresholds = new int[] {
+            GSM_RSSI_POOR, GSM_RSSI_MODERATE, GSM_RSSI_GOOD, GSM_RSSI_GREAT};
 
     private int mRssi; // in dBm [-113, -51] or UNAVAILABLE
     @UnsupportedAppUsage
@@ -53,7 +58,7 @@
 
     /** @hide */
     public CellSignalStrengthGsm(int rssi, int ber, int ta) {
-        mRssi = inRangeOrUnavailable(rssi, -113, -51);
+        mRssi = inRangeOrUnavailable(rssi, GSM_RSSI_MIN, GSM_RSSI_MAX);
         mBitErrorRate = inRangeOrUnavailable(ber, 0, 7, 99);
         mTimingAdvance = inRangeOrUnavailable(ta, 0, 219);
         updateLevel(null, null);
@@ -97,13 +102,9 @@
         mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
     }
 
-    /**
-     * Retrieve an abstract level value for the overall signal strength.
-     *
-     * @return a single integer from 0 to 4 representing the general signal quality.
-     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
-     */
+    /** {@inheritDoc} */
     @Override
+    @IntRange(from = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to = SIGNAL_STRENGTH_GREAT)
     public int getLevel() {
         return mLevel;
     }
@@ -111,12 +112,22 @@
     /** @hide */
     @Override
     public void updateLevel(PersistableBundle cc, ServiceState ss) {
-        if (mRssi > GSM_RSSI_MAX) mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
-        else if (mRssi >= GSM_RSSI_GREAT) mLevel = SIGNAL_STRENGTH_GREAT;
-        else if (mRssi >= GSM_RSSI_GOOD)  mLevel = SIGNAL_STRENGTH_GOOD;
-        else if (mRssi >= GSM_RSSI_MODERATE)  mLevel = SIGNAL_STRENGTH_MODERATE;
-        else if (mRssi >= GSM_RSSI_POOR) mLevel = SIGNAL_STRENGTH_POOR;
-        else mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        int[] rssiThresholds;
+        if (cc == null) {
+            rssiThresholds = sRssiThresholds;
+        } else {
+            rssiThresholds = cc.getIntArray(CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY);
+            if (rssiThresholds == null || rssiThresholds.length != NUM_SIGNAL_STRENGTH_THRESHOLDS) {
+                rssiThresholds = sRssiThresholds;
+            }
+        }
+        int level = NUM_SIGNAL_STRENGTH_THRESHOLDS;
+        if (mRssi < GSM_RSSI_MIN || mRssi > GSM_RSSI_MAX) {
+            mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+            return;
+        }
+        while (level > 0 && mRssi < rssiThresholds[level - 1]) level--;
+        mLevel = level;
     }
 
     /**
@@ -141,7 +152,7 @@
     /**
      * Get the RSSI in ASU.
      *
-     * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
+     * Asu is calculated based on 3GPP RSSI. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
      *
      * @return RSSI in ASU 0..31, 99, or UNAVAILABLE
      */
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 5687ada..2272dc9 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.IntRange;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -145,13 +146,9 @@
         mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
     }
 
-    /**
-     * Retrieve an abstract level value for the overall signal strength.
-     *
-     * @return a single integer from 0 to 4 representing the general signal quality.
-     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
-     */
+    /** {@inheritDoc} */
     @Override
+    @IntRange(from = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to = SIGNAL_STRENGTH_GREAT)
     public int getLevel() {
         return mLevel;
     }
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index fff3adf..1912c60 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.IntRange;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
@@ -183,7 +184,9 @@
         mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
     }
 
+    /** {@inheritDoc} */
     @Override
+    @IntRange(from = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to = SIGNAL_STRENGTH_GREAT)
     public int getLevel() {
         return mLevel;
     }
@@ -227,6 +230,9 @@
         return asuLevel;
     }
 
+    /**
+     * Get the CSI-RSRP as dBm value -140..-44dBm or {@link CellInfo#UNAVAILABLE UNAVAILABLE}.
+     */
     @Override
     public int getDbm() {
         return mCsiRsrp;
diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
index b562f32..f4a3dbb 100644
--- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -121,13 +122,10 @@
         mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
     }
 
-    /**
-     * Retrieve an abstract level value for the overall signal strength.
-     *
-     * @return a single integer from 0 to 4 representing the general signal quality.
-     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
-     */
+
+    /** {@inheritDoc} */
     @Override
+    @IntRange(from = 0, to = 4)
     public int getLevel() {
         return mLevel;
     }
@@ -144,7 +142,7 @@
     }
 
     /**
-     * Get the signal strength as dBm
+     * Get the RSCP as dBm value -120..-24dBm or {@link CellInfo#UNAVAILABLE UNAVAILABLE}.
      */
     @Override
     public int getDbm() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index 8efc0f2..1693252 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.IntRange;
 import android.annotation.StringDef;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -143,13 +144,9 @@
         mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
     }
 
-    /**
-     * Retrieve an abstract level value for the overall signal strength.
-     *
-     * @return a single integer from 0 to 4 representing the general signal quality.
-     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
-     */
+    /** {@inheritDoc} */
     @Override
+    @IntRange(from = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to = SIGNAL_STRENGTH_GREAT)
     public int getLevel() {
         return mLevel;
     }
@@ -202,7 +199,7 @@
     }
 
     /**
-     * Get the signal strength as dBm
+     * Get the RSCP as dBm value -120..-24dBm or {@link CellInfo#UNAVAILABLE UNAVAILABLE}.
      */
     @Override
     public int getDbm() {