Merge "Fix a few issues in WindowDecor" into tm-qpr-dev
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e837920..8b41aa4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8468,8 +8468,8 @@
             }
 
             int maxAvatarSize = resources.getDimensionPixelSize(
-                    isLowRam ? R.dimen.notification_person_icon_max_size
-                            : R.dimen.notification_person_icon_max_size_low_ram);
+                    isLowRam ? R.dimen.notification_person_icon_max_size_low_ram
+                            : R.dimen.notification_person_icon_max_size);
             if (mUser != null && mUser.getIcon() != null) {
                 mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
             }
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java
new file mode 100644
index 0000000..65e8c64
--- /dev/null
+++ b/core/java/com/android/internal/app/AppLocaleCollector.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 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.internal.app;
+
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.LocaleList;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/** The Locale data collector for per-app language. */
+class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+    private static final String TAG = AppLocaleCollector.class.getSimpleName();
+    private final Context mContext;
+    private final String mAppPackageName;
+    private final LocaleStore.LocaleInfo mAppCurrentLocale;
+
+    AppLocaleCollector(Context context, String appPackageName) {
+        mContext = context;
+        mAppPackageName = appPackageName;
+        mAppCurrentLocale = LocaleStore.getAppCurrentLocaleInfo(
+                mContext, mAppPackageName);
+    }
+
+    @Override
+    public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
+        HashSet<String> langTagsToIgnore = new HashSet<>();
+
+        LocaleList systemLangList = LocaleList.getDefault();
+        for(int i = 0; i < systemLangList.size(); i++) {
+            langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
+        }
+
+        if (mAppCurrentLocale != null) {
+            langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag());
+        }
+        return langTagsToIgnore;
+    }
+
+    @Override
+    public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, boolean isForCountryMode) {
+        AppLocaleStore.AppLocaleResult result =
+                AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName);
+        Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
+        Set<LocaleStore.LocaleInfo> appLocaleList = new HashSet<>();
+        Set<LocaleStore.LocaleInfo> systemLocaleList;
+        boolean shouldShowList =
+                result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG
+                        || result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_ASSET;
+
+        // Get system supported locale list
+        if (isForCountryMode) {
+            systemLocaleList = LocaleStore.getLevelLocales(mContext,
+                    langTagsToIgnore, parent, translatedOnly);
+        } else {
+            systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
+                    null /* no parent */, translatedOnly);
+        }
+
+        // Add current app locale
+        if (mAppCurrentLocale != null && !isForCountryMode) {
+            appLocaleList.add(mAppCurrentLocale);
+        }
+
+        // Add current system language into suggestion list
+        for(LocaleStore.LocaleInfo localeInfo:
+                LocaleStore.getSystemCurrentLocaleInfo()) {
+            boolean isNotCurrentLocale = mAppCurrentLocale == null
+                    || !localeInfo.getLocale().equals(mAppCurrentLocale.getLocale());
+            if (!isForCountryMode && isNotCurrentLocale) {
+                appLocaleList.add(localeInfo);
+            }
+        }
+
+        // Add the languages that included in system supported locale
+        if (shouldShowList) {
+            appLocaleList.addAll(filterTheLanguagesNotIncludedInSystemLocale(
+                    systemLocaleList, result.mAppSupportedLocales));
+        }
+
+        // Add "system language" option
+        if (!isForCountryMode && shouldShowList) {
+            appLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(
+                    mAppCurrentLocale == null));
+        }
+
+        if (Build.isDebuggable()) {
+            Log.d(TAG, "App locale list: " + appLocaleList);
+        }
+
+        return appLocaleList;
+    }
+
+    @Override
+    public boolean hasSpecificPackageName() {
+        return true;
+    }
+
+    private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotIncludedInSystemLocale(
+            Set<LocaleStore.LocaleInfo> systemLocaleList,
+            HashSet<Locale> appSupportedLocales) {
+        Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
+
+        for(LocaleStore.LocaleInfo li: systemLocaleList) {
+            if (appSupportedLocales.contains(li.getLocale())) {
+                filteredList.add(li);
+            } else {
+                for(Locale l: appSupportedLocales) {
+                    if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
+                        filteredList.add(li);
+                        break;
+                    }
+                }
+            }
+        }
+        return filteredList;
+    }
+}
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 965895f..3efd279 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -16,16 +16,12 @@
 
 package com.android.internal.app;
 
-import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus;
-
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.app.ListFragment;
 import android.content.Context;
 import android.os.Bundle;
-import android.os.LocaleList;
 import android.text.TextUtils;
-import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -36,7 +32,6 @@
 
 import com.android.internal.R;
 
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
@@ -54,6 +49,7 @@
 
     private SuggestedLocaleAdapter mAdapter;
     private LocaleSelectedListener mListener;
+    private LocaleCollectorBase mLocalePickerCollector;
     private Set<LocaleStore.LocaleInfo> mLocaleList;
     private LocaleStore.LocaleInfo mParentLocale;
     private boolean mTranslatedOnly = false;
@@ -62,7 +58,6 @@
     private boolean mPreviousSearchHadFocus = false;
     private int mFirstVisiblePosition = 0;
     private int mTopDistance = 0;
-    private String mAppPackageName;
     private CharSequence mTitle = null;
     private OnActionExpandListener mOnActionExpandListener;
 
@@ -79,31 +74,50 @@
         void onLocaleSelected(LocaleStore.LocaleInfo locale);
     }
 
-    private static LocalePickerWithRegion createCountryPicker(Context context,
+    /**
+     * The interface which provides the locale list.
+     */
+    interface LocaleCollectorBase {
+        /** Gets the ignored locale list. */
+        HashSet<String> getIgnoredLocaleList(boolean translatedOnly);
+
+        /** Gets the supported locale list. */
+        Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+                boolean translatedOnly, boolean isForCountryMode);
+
+        /** Indicates if the class work for specific package. */
+        boolean hasSpecificPackageName();
+    }
+
+    private static LocalePickerWithRegion createCountryPicker(
             LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
-            boolean translatedOnly, String appPackageName,
-            OnActionExpandListener onActionExpandListener) {
+            boolean translatedOnly, OnActionExpandListener onActionExpandListener,
+            LocaleCollectorBase localePickerCollector) {
         LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
         localePicker.setOnActionExpandListener(onActionExpandListener);
-        boolean shouldShowTheList = localePicker.setListener(context, listener, parent,
-                translatedOnly, appPackageName);
+        boolean shouldShowTheList = localePicker.setListener(listener, parent,
+                translatedOnly, localePickerCollector);
         return shouldShowTheList ? localePicker : null;
     }
 
     public static LocalePickerWithRegion createLanguagePicker(Context context,
             LocaleSelectedListener listener, boolean translatedOnly) {
-        LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
-        localePicker.setListener(context, listener, /* parent */ null, translatedOnly, null);
-        return localePicker;
+        return createLanguagePicker(context, listener, translatedOnly, null, null);
     }
 
     public static LocalePickerWithRegion createLanguagePicker(Context context,
             LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,
             OnActionExpandListener onActionExpandListener) {
+        LocaleCollectorBase localePickerController;
+        if (TextUtils.isEmpty(appPackageName)) {
+            localePickerController = new SystemLocaleCollector(context);
+        } else {
+            localePickerController = new AppLocaleCollector(context, appPackageName);
+        }
         LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
         localePicker.setOnActionExpandListener(onActionExpandListener);
-        localePicker.setListener(
-                context, listener, /* parent */ null, translatedOnly, appPackageName);
+        localePicker.setListener(listener, /* parent */ null, translatedOnly,
+                localePickerController);
         return localePicker;
     }
 
@@ -120,109 +134,23 @@
      * In this case we don't even show the list, we call the listener with that locale,
      * "pretending" it was selected, and return false.</p>
      */
-    private boolean setListener(Context context, LocaleSelectedListener listener,
-            LocaleStore.LocaleInfo parent, boolean translatedOnly, String appPackageName) {
+    private boolean setListener(LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, LocaleCollectorBase localePickerController) {
         this.mParentLocale = parent;
         this.mListener = listener;
         this.mTranslatedOnly = translatedOnly;
-        this.mAppPackageName = appPackageName;
+        this.mLocalePickerCollector = localePickerController;
         setRetainInstance(true);
 
-        final HashSet<String> langTagsToIgnore = new HashSet<>();
-        LocaleStore.LocaleInfo appCurrentLocale =
-                LocaleStore.getAppCurrentLocaleInfo(context, appPackageName);
-        boolean isForCountryMode = parent != null;
+        mLocaleList = localePickerController.getSupportedLocaleList(
+                parent, translatedOnly, parent != null);
 
-        if (!TextUtils.isEmpty(appPackageName) && !isForCountryMode) {
-            // Filter current system locale to add them into suggestion
-            LocaleList systemLangList = LocaleList.getDefault();
-            for(int i = 0; i < systemLangList.size(); i++) {
-                langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
-            }
-
-            if (appCurrentLocale != null) {
-                Log.d(TAG, "appCurrentLocale: " + appCurrentLocale.getLocale().toLanguageTag());
-                langTagsToIgnore.add(appCurrentLocale.getLocale().toLanguageTag());
-            } else {
-                Log.d(TAG, "appCurrentLocale is null");
-            }
-        } else if (!translatedOnly) {
-            final LocaleList userLocales = LocalePicker.getLocales();
-            final String[] langTags = userLocales.toLanguageTags().split(",");
-            Collections.addAll(langTagsToIgnore, langTags);
-        }
-
-        if (isForCountryMode) {
-            mLocaleList = LocaleStore.getLevelLocales(context,
-                    langTagsToIgnore, parent, translatedOnly);
-            if (mLocaleList.size() <= 1) {
-                if (listener != null && (mLocaleList.size() == 1)) {
-                    listener.onLocaleSelected(mLocaleList.iterator().next());
-                }
-                return false;
-            }
+        if (parent != null && listener != null && mLocaleList.size() == 1) {
+            listener.onLocaleSelected(mLocaleList.iterator().next());
+            return false;
         } else {
-            mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore,
-                    null /* no parent */, translatedOnly);
+            return true;
         }
-        Log.d(TAG, "mLocaleList size:  " + mLocaleList.size());
-
-        // Adding current locale and system default option into suggestion list
-        if(!TextUtils.isEmpty(appPackageName)) {
-            if (appCurrentLocale != null && !isForCountryMode) {
-                mLocaleList.add(appCurrentLocale);
-            }
-
-            AppLocaleStore.AppLocaleResult result =
-                    AppLocaleStore.getAppSupportedLocales(context, appPackageName);
-            boolean shouldShowList =
-                    result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG
-                    || result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
-
-            // Add current system language into suggestion list
-            for(LocaleStore.LocaleInfo localeInfo: LocaleStore.getSystemCurrentLocaleInfo()) {
-                boolean isNotCurrentLocale = appCurrentLocale == null
-                        || !localeInfo.getLocale().equals(appCurrentLocale.getLocale());
-                if (!isForCountryMode && isNotCurrentLocale) {
-                    mLocaleList.add(localeInfo);
-                }
-            }
-
-            // Filter the language not support in app
-            mLocaleList = filterTheLanguagesNotSupportedInApp(
-                    shouldShowList, result.mAppSupportedLocales);
-
-            Log.d(TAG, "mLocaleList after app-supported filter:  " + mLocaleList.size());
-
-            // Add "system language"
-            if (!isForCountryMode && shouldShowList) {
-                mLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(appCurrentLocale == null));
-            }
-        }
-        return true;
-    }
-
-    private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotSupportedInApp(
-            boolean shouldShowList, HashSet<Locale> supportedLocales) {
-        Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
-        if (!shouldShowList) {
-            return filteredList;
-        }
-
-        for(LocaleStore.LocaleInfo li: mLocaleList) {
-            if (supportedLocales.contains(li.getLocale())) {
-                filteredList.add(li);
-            } else {
-                for(Locale l: supportedLocales) {
-                    if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
-                        filteredList.add(li);
-                        break;
-                    }
-                }
-            }
-        }
-
-        return filteredList;
     }
 
     private void returnToParentFrame() {
@@ -246,7 +174,9 @@
         mTitle = getActivity().getTitle();
         final boolean countryMode = mParentLocale != null;
         final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault();
-        mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, mAppPackageName);
+        final boolean hasSpecificPackageName =
+                mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName();
+        mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);
         final LocaleHelper.LocaleInfoComparator comp =
                 new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
         mAdapter.sort(comp);
@@ -321,8 +251,8 @@
             returnToParentFrame();
         } else {
             LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
-                    getContext(), mListener, locale, mTranslatedOnly /* translate only */,
-                    mAppPackageName, mOnActionExpandListener);
+                    mListener, locale, mTranslatedOnly /* translate only */,
+                    mOnActionExpandListener, this.mLocalePickerCollector);
             if (selector != null) {
                 getFragmentManager().beginTransaction()
                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
@@ -340,7 +270,8 @@
             inflater.inflate(R.menu.language_selection_list, menu);
 
             final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu);
-            if (!TextUtils.isEmpty(mAppPackageName) && mOnActionExpandListener != null) {
+            if (mLocalePickerCollector.hasSpecificPackageName()
+                    && mOnActionExpandListener != null) {
                 searchMenuItem.setOnActionExpandListener(mOnActionExpandListener);
             }
 
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index 1be1247..5ed0e8b 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -70,17 +70,17 @@
     private Locale mDisplayLocale = null;
     // used to potentially cache a modified Context that uses mDisplayLocale
     private Context mContextOverride = null;
-    private String mAppPackageName;
+    private boolean mHasSpecificAppPackageName;
 
     public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) {
-        this(localeOptions, countryMode, null);
+        this(localeOptions, countryMode, false);
     }
 
     public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode,
-            String appPackageName) {
+            boolean hasSpecificAppPackageName) {
         mCountryMode = countryMode;
         mLocaleOptions = new ArrayList<>(localeOptions.size());
-        mAppPackageName = appPackageName;
+        mHasSpecificAppPackageName = hasSpecificAppPackageName;
 
         for (LocaleStore.LocaleInfo li : localeOptions) {
             if (li.isSuggested()) {
@@ -134,7 +134,7 @@
 
     @Override
     public int getViewTypeCount() {
-        if (!TextUtils.isEmpty(mAppPackageName) && showHeaders()) {
+        if (mHasSpecificAppPackageName && showHeaders()) {
             // Two headers, 1 "System language", 1 current locale
             return APP_LANGUAGE_PICKER_TYPE_COUNT;
         } else if (showHeaders()) {
diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java
new file mode 100644
index 0000000..9a6d4c1
--- /dev/null
+++ b/core/java/com/android/internal/app/SystemLocaleCollector.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.internal.app;
+
+import android.content.Context;
+import android.os.LocaleList;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** The Locale data collector for System language. */
+class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+    private final Context mContext;
+
+    SystemLocaleCollector(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
+        HashSet<String> ignoreList = new HashSet<>();
+        if (!translatedOnly) {
+            final LocaleList userLocales = LocalePicker.getLocales();
+            final String[] langTags = userLocales.toLanguageTags().split(",");
+            Collections.addAll(ignoreList, langTags);
+        }
+        return ignoreList;
+    }
+
+    @Override
+    public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, boolean isForCountryMode) {
+        Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
+        Set<LocaleStore.LocaleInfo> localeList;
+
+        if (isForCountryMode) {
+            localeList = LocaleStore.getLevelLocales(mContext,
+                    langTagsToIgnore, parent, translatedOnly);
+        } else {
+            localeList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
+                    null /* no parent */, translatedOnly);
+        }
+        return localeList;
+    }
+
+
+    @Override
+    public boolean hasSpecificPackageName() {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 2a3f916..e8e5a51 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1970,8 +1970,8 @@
     <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Presiona para ver archivos"</string>
     <string name="pin_target" msgid="8036028973110156895">"Fijar"</string>
     <string name="pin_specific_target" msgid="7824671240625957415">"Fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
-    <string name="unpin_target" msgid="3963318576590204447">"No fijar"</string>
-    <string name="unpin_specific_target" msgid="3859828252160908146">"No fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="unpin_target" msgid="3963318576590204447">"Dejar de fijar"</string>
+    <string name="unpin_specific_target" msgid="3859828252160908146">"Dejar de fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="app_info" msgid="6113278084877079851">"Información de apps"</string>
     <string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demostración…"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 79faaac..0daa324 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1139,7 +1139,7 @@
     <string name="copy" msgid="5472512047143665218">"कॉपी करें"</string>
     <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"क्लिपबोर्ड पर कॉपी नहीं हो सका"</string>
     <string name="paste" msgid="461843306215520225">"चिपकाएं"</string>
-    <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे पाठ के रूप में चिपकाएं"</string>
+    <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे टेक्स्ट के रूप में चिपकाएं"</string>
     <string name="replace" msgid="7842675434546657444">"बदलें•"</string>
     <string name="delete" msgid="1514113991712129054">"मिटाएं"</string>
     <string name="copyUrl" msgid="6229645005987260230">"यूआरएल को कॉपी करें"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 9ab64e6..d11eca6 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -252,7 +252,7 @@
     <string name="bugreport_message" msgid="5212529146119624326">"현재 기기 상태에 대한 정보를 수집하여 이메일 메시지로 전송합니다. 버그 신고를 시작하여 전송할 준비가 되려면 약간 시간이 걸립니다."</string>
     <string name="bugreport_option_interactive_title" msgid="7968287837902871289">"대화형 보고서"</string>
     <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"대부분의 경우 이 옵션을 사용합니다. 신고 진행 상황을 추적하고 문제에 대한 세부정보를 입력하고 스크린샷을 찍을 수 있습니다. 신고하기에 시간이 너무 오래 걸리고 사용 빈도가 낮은 일부 섹션을 생략할 수 있습니다."</string>
-    <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 보고서"</string>
+    <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 신고"</string>
     <string name="bugreport_option_full_summary" msgid="1975130009258435885">"기기가 응답하지 않거나 너무 느리거나 모든 보고서 섹션이 필요한 경우 이 옵션을 사용하여 시스템 방해를 최소화합니다. 세부정보를 추가하거나 스크린샷을 추가로 찍을 수 없습니다."</string>
     <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{버그 신고 스크린샷을 #초 후에 찍습니다.}other{버그 신고 스크린샷을 #초 후에 찍습니다.}}"</string>
     <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"버그 신고용 스크린샷 촬영 완료"</string>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 0b8b29b..bcb13d2 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -48,6 +48,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertEquals;
@@ -56,7 +57,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
 import android.app.Notification.CallStyle;
@@ -68,6 +71,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
+import android.graphics.Typeface;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Build;
@@ -79,7 +83,9 @@
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
 import android.text.style.TextAppearanceSpan;
+import android.util.Pair;
 import android.widget.RemoteViews;
 
 import androidx.test.InstrumentationRegistry;
@@ -89,6 +95,8 @@
 import com.android.internal.R;
 import com.android.internal.util.ContrastColorUtil;
 
+import junit.framework.Assert;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -218,8 +226,10 @@
 
     @Test
     public void allPendingIntents_recollectedAfterReusingBuilder() {
-        PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
-        PendingIntent intent2 = PendingIntent.getActivity(mContext, 0, new Intent("test2"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent intent1 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        PendingIntent intent2 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test2"), PendingIntent.FLAG_IMMUTABLE);
 
         Notification.Builder builder = new Notification.Builder(mContext, "channel");
         builder.setContentIntent(intent1);
@@ -669,30 +679,23 @@
         Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
                 style).build();
 
+        int targetSize = mContext.getResources().getDimensionPixelSize(
+                ActivityManager.isLowRamDeviceStatic()
+                        ? R.dimen.notification_person_icon_max_size_low_ram
+                        : R.dimen.notification_person_icon_max_size);
+
         Bitmap personIcon = style.getUser().getIcon().getBitmap();
-        assertThat(personIcon.getWidth()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
-        assertThat(personIcon.getHeight()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
+        assertThat(personIcon.getWidth()).isEqualTo(targetSize);
+        assertThat(personIcon.getHeight()).isEqualTo(targetSize);
 
         Bitmap avatarIcon = style.getMessages().get(0).getSenderPerson().getIcon().getBitmap();
-        assertThat(avatarIcon.getWidth()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
-        assertThat(avatarIcon.getHeight()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
+        assertThat(avatarIcon.getWidth()).isEqualTo(targetSize);
+        assertThat(avatarIcon.getHeight()).isEqualTo(targetSize);
 
         Bitmap historicAvatarIcon = style.getHistoricMessages().get(
                 0).getSenderPerson().getIcon().getBitmap();
-        assertThat(historicAvatarIcon.getWidth()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
-        assertThat(historicAvatarIcon.getHeight()).isEqualTo(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.notification_person_icon_max_size));
+        assertThat(historicAvatarIcon.getWidth()).isEqualTo(targetSize);
+        assertThat(historicAvatarIcon.getHeight()).isEqualTo(targetSize);
     }
 
     @Test
@@ -780,7 +783,6 @@
         assertFalse(notification.isMediaNotification());
     }
 
-    @Test
     public void validateColorizedPaletteForColor(int rawColor) {
         Notification.Colors cDay = new Notification.Colors();
         Notification.Colors cNight = new Notification.Colors();
@@ -861,19 +863,22 @@
         Bundle fakeTypes = new Bundle();
         fakeTypes.putParcelable(EXTRA_LARGE_ICON_BIG, new Bundle());
 
-        style.restoreFromExtras(fakeTypes);
 
         // no crash, good
     }
 
     @Test
     public void testRestoreFromExtras_Messaging_invalidExtra_noCrash() {
-        Notification.Style style = new Notification.MessagingStyle();
+        Notification.Style style = new Notification.MessagingStyle("test");
         Bundle fakeTypes = new Bundle();
         fakeTypes.putParcelable(EXTRA_MESSAGING_PERSON, new Bundle());
         fakeTypes.putParcelable(EXTRA_CONVERSATION_ICON, new Bundle());
 
-        style.restoreFromExtras(fakeTypes);
+        Notification n = new Notification.Builder(mContext, "test")
+                .setStyle(style)
+                .setExtras(fakeTypes)
+                .build();
+        Notification.Builder.recoverBuilder(mContext, n);
 
         // no crash, good
     }
@@ -885,22 +890,33 @@
         fakeTypes.putParcelable(EXTRA_MEDIA_SESSION, new Bundle());
         fakeTypes.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, new Bundle());
 
-        style.restoreFromExtras(fakeTypes);
+        Notification n = new Notification.Builder(mContext, "test")
+                .setStyle(style)
+                .setExtras(fakeTypes)
+                .build();
+        Notification.Builder.recoverBuilder(mContext, n);
 
         // no crash, good
     }
 
     @Test
     public void testRestoreFromExtras_Call_invalidExtra_noCrash() {
-        Notification.Style style = new CallStyle();
+        PendingIntent intent1 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        Notification.Style style = Notification.CallStyle.forIncomingCall(
+                new Person.Builder().setName("hi").build(), intent1, intent1);
+
         Bundle fakeTypes = new Bundle();
         fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle());
         fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle());
         fakeTypes.putParcelable(EXTRA_DECLINE_INTENT, new Bundle());
         fakeTypes.putParcelable(EXTRA_HANG_UP_INTENT, new Bundle());
 
-        style.restoreFromExtras(fakeTypes);
-
+        Notification n = new Notification.Builder(mContext, "test")
+                .setStyle(style)
+                .setExtras(fakeTypes)
+                .build();
+        Notification.Builder.recoverBuilder(mContext, n);
         // no crash, good
     }
 
@@ -962,7 +978,11 @@
         fakeTypes.putParcelable(KEY_ON_READ, new Bundle());
         fakeTypes.putParcelable(KEY_ON_REPLY, new Bundle());
         fakeTypes.putParcelable(KEY_REMOTE_INPUT, new Bundle());
-        Notification.CarExtender.UnreadConversation.getUnreadConversationFromBundle(fakeTypes);
+
+        Notification n = new Notification.Builder(mContext, "test")
+                .setExtras(fakeTypes)
+                .build();
+        Notification.CarExtender extender = new Notification.CarExtender(n);
 
         // no crash, good
     }
@@ -980,6 +1000,493 @@
         // no crash, good
     }
 
+
+    @Test
+    public void testDoesNotStripsExtenders() {
+        Notification.Builder nb = new Notification.Builder(mContext, "channel");
+        nb.extend(new Notification.CarExtender().setColor(Color.RED));
+        nb.extend(new Notification.TvExtender().setChannelId("different channel"));
+        nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
+        Notification before = nb.build();
+        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
+
+        assertTrue(before == after);
+
+        Assert.assertEquals("different channel",
+                new Notification.TvExtender(before).getChannelId());
+        Assert.assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
+        Assert.assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
+    }
+
+    @Test
+    public void testStyleChangeVisiblyDifferent_noStyles() {
+        Notification.Builder n1 = new Notification.Builder(mContext, "test");
+        Notification.Builder n2 = new Notification.Builder(mContext, "test");
+
+        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testStyleChangeVisiblyDifferent_noStyleToStyle() {
+        Notification.Builder n1 = new Notification.Builder(mContext, "test");
+        Notification.Builder n2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle());
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testStyleChangeVisiblyDifferent_styleToNoStyle() {
+        Notification.Builder n2 = new Notification.Builder(mContext, "test");
+        Notification.Builder n1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle());
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testStyleChangeVisiblyDifferent_changeStyle() {
+        Notification.Builder n1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.InboxStyle());
+        Notification.Builder n2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle());
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testInboxTextChange() {
+        Notification.Builder nInbox1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.InboxStyle().addLine("a").addLine("b"));
+        Notification.Builder nInbox2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.InboxStyle().addLine("b").addLine("c"));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2));
+    }
+
+    @Test
+    public void testBigTextTextChange() {
+        Notification.Builder nBigText1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle().bigText("something"));
+        Notification.Builder nBigText2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle().bigText("else"));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2));
+    }
+
+    @Test
+    public void testBigPictureChange() {
+        Bitmap bitA = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+        Bitmap bitB = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
+
+        Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigPictureStyle().bigPicture(bitA));
+        Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigPictureStyle().bigPicture(bitB));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2));
+    }
+
+    @Test
+    public void testMessagingChange_text() {
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build())));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build()))
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "b", 100, new Person.Builder().setName("hi").build()))
+                );
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testMessagingChange_data() {
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build())
+                                .setData("text", mock(Uri.class))));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build())));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testMessagingChange_sender() {
+        Person a = new Person.Builder().setName("A").build();
+        Person b = new Person.Builder().setName("b").build();
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testMessagingChange_key() {
+        Person a = new Person.Builder().setName("hi").setKey("A").build();
+        Person b = new Person.Builder().setName("hi").setKey("b").build();
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
+
+        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testMessagingChange_ignoreTimeChange() {
+        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 100, new Person.Builder().setName("hi").build())));
+        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "a", 1000, new Person.Builder().setName("hi").build()))
+                );
+
+        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+    }
+
+    @Test
+    public void testRemoteViews_nullChange() {
+        Notification.Builder n1 = new Notification.Builder(mContext, "test")
+                .setContent(mock(RemoteViews.class));
+        Notification.Builder n2 = new Notification.Builder(mContext, "test");
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test");
+        n2 = new Notification.Builder(mContext, "test")
+                .setContent(mock(RemoteViews.class));
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test")
+                .setCustomBigContentView(mock(RemoteViews.class));
+        n2 = new Notification.Builder(mContext, "test");
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test");
+        n2 = new Notification.Builder(mContext, "test")
+                .setCustomBigContentView(mock(RemoteViews.class));
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test");
+        n2 = new Notification.Builder(mContext, "test");
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testRemoteViews_layoutChange() {
+        RemoteViews a = mock(RemoteViews.class);
+        when(a.getLayoutId()).thenReturn(234);
+        RemoteViews b = mock(RemoteViews.class);
+        when(b.getLayoutId()).thenReturn(189);
+
+        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testRemoteViews_layoutSame() {
+        RemoteViews a = mock(RemoteViews.class);
+        when(a.getLayoutId()).thenReturn(234);
+        RemoteViews b = mock(RemoteViews.class);
+        when(b.getLayoutId()).thenReturn(234);
+
+        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testRemoteViews_sequenceChange() {
+        RemoteViews a = mock(RemoteViews.class);
+        when(a.getLayoutId()).thenReturn(234);
+        when(a.getSequenceNumber()).thenReturn(1);
+        RemoteViews b = mock(RemoteViews.class);
+        when(b.getLayoutId()).thenReturn(234);
+        when(b.getSequenceNumber()).thenReturn(2);
+
+        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testRemoteViews_sequenceSame() {
+        RemoteViews a = mock(RemoteViews.class);
+        when(a.getLayoutId()).thenReturn(234);
+        when(a.getSequenceNumber()).thenReturn(1);
+        RemoteViews b = mock(RemoteViews.class);
+        when(b.getLayoutId()).thenReturn(234);
+        when(b.getSequenceNumber()).thenReturn(1);
+
+        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferent_null() {
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentSame() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentText() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
+                .build();
+
+        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentSpannables() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon,
+                        new SpannableStringBuilder().append("test1",
+                                new StyleSpan(Typeface.BOLD),
+                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
+                        intent).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "test1", intent).build())
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentNumber() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
+                .build();
+
+        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsDifferentIntent() {
+        PendingIntent intent1 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        PendingIntent intent2 = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build())
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testActionsIgnoresRemoteInputs() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        Notification n1 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(new RemoteInput.Builder("a")
+                                .setChoices(new CharSequence[] {"i", "m"})
+                                .build())
+                        .build())
+                .build();
+        Notification n2 = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(new RemoteInput.Builder("a")
+                                .setChoices(new CharSequence[] {"t", "m"})
+                                .build())
+                        .build())
+                .build();
+
+        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+    }
+
+    @Test
+    public void testFreeformRemoteInputActionPair_noRemoteInput() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+        Notification notification = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .build())
+                .build();
+        Assert.assertNull(notification.findRemoteInputActionPair(false));
+    }
+
+    @Test
+    public void testFreeformRemoteInputActionPair_hasRemoteInput() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        RemoteInput remoteInput = new RemoteInput.Builder("a").build();
+
+        Notification.Action actionWithRemoteInput =
+                new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(remoteInput)
+                        .addRemoteInput(remoteInput)
+                        .build();
+
+        Notification.Action actionWithoutRemoteInput =
+                new Notification.Action.Builder(icon, "TEXT 2", intent)
+                        .build();
+
+        Notification notification = new Notification.Builder(mContext, "test")
+                .addAction(actionWithoutRemoteInput)
+                .addAction(actionWithRemoteInput)
+                .build();
+
+        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
+                notification.findRemoteInputActionPair(false);
+
+        assertNotNull(remoteInputActionPair);
+        Assert.assertEquals(remoteInput, remoteInputActionPair.first);
+        Assert.assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
+    }
+
+    @Test
+    public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+        Notification notification = new Notification.Builder(mContext, "test")
+                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(
+                                new RemoteInput.Builder("a")
+                                        .setAllowFreeFormInput(false).build())
+                        .build())
+                .build();
+        Assert.assertNull(notification.findRemoteInputActionPair(true));
+    }
+
+    @Test
+    public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
+        PendingIntent intent = PendingIntent.getActivity(
+                mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+        RemoteInput remoteInput =
+                new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
+        RemoteInput freeformRemoteInput =
+                new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();
+
+        Notification.Action actionWithFreeformRemoteInput =
+                new Notification.Action.Builder(icon, "TEXT 1", intent)
+                        .addRemoteInput(remoteInput)
+                        .addRemoteInput(freeformRemoteInput)
+                        .build();
+
+        Notification.Action actionWithoutFreeformRemoteInput =
+                new Notification.Action.Builder(icon, "TEXT 2", intent)
+                        .addRemoteInput(remoteInput)
+                        .build();
+
+        Notification notification = new Notification.Builder(mContext, "test")
+                .addAction(actionWithoutFreeformRemoteInput)
+                .addAction(actionWithFreeformRemoteInput)
+                .build();
+
+        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
+                notification.findRemoteInputActionPair(true);
+
+        assertNotNull(remoteInputActionPair);
+        Assert.assertEquals(freeformRemoteInput, remoteInputActionPair.first);
+        Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
+    }
+
     private void assertValid(Notification.Colors c) {
         // Assert that all colors are populated
         assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 0fdfed8..17b1d1b 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"መያዣ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 351e192..2babc3e 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string>
     <string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"رجوع"</string>
+    <string name="handle_text" msgid="1766582106752184456">"مقبض"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 765aaba..fe43cd6 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string>
     <string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string>
+    <string name="handle_text" msgid="1766582106752184456">"হেণ্ডেল"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 0ec5db1..c22f542 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kiçildin"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Hər kəsə açıq istifadəçi adı"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index bd4a079..cf5394b 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index a04a50d..c525f52 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Манипулатор"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 2cc0ab3..bec284d 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -84,6 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiziranje"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
-    <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string>
-    <string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string>
+    <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index a2badaf..c84bb48 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Enrere"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Ansa"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 084ea86..60c1f85 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Håndtag"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 195c335..b57f0c8 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Zurück"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Ziehpunkt"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 0ad387b..aed03ac 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 4b85a22..bddc2c3 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index cc90578..696a520 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Käepide"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index a1d0b58..7550a09 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
     <string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string>
+    <string name="handle_text" msgid="1766582106752184456">"دستگیره"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 5df1e04..a79adf8 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Kahva"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index c4b386a..865e2dc 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Poignée"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index f5a106d..47e6696 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 95484d5..2c643de 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string>
+    <string name="handle_text" msgid="1766582106752184456">"हैंडल"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 5ae72eb..d3bca33 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Հետ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Նշիչ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index d36a83f..f157fcf 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Tuas"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 31d483e..ead1757 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Til baka"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handfang"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 5c9425e..12c962d 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
     <string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"חזרה"</string>
+    <string name="handle_text" msgid="1766582106752184456">"נקודת אחיזה"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 787ac3e..b284361 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string>
     <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"უკან"</string>
+    <string name="handle_text" msgid="1766582106752184456">"იდენტიფიკატორი"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 927f0d7..2d842b8 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Артқа"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 955b2f8..3243a64 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string>
     <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ឈ្មោះអ្នកប្រើប្រាស់"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index a69e105..3a655b8 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"최소화"</string>
     <string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"뒤로"</string>
+    <string name="handle_text" msgid="1766582106752184456">"핸들"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 15bde88..b800e3e 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ມືບັງຄັບ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 275efa2..94339a4 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Sumažinti"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Atgal"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Rankenėlė"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 3048630..d282453 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizēt"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Turis"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 9013828..5db8d6d 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string>
     <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ഹാൻഡിൽ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index dc884e9..779cf5c 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string>
+    <string name="handle_text" msgid="1766582106752184456">"हँडल"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index a2f24f2..0ceee2d 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Håndtak"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 6a70d8d..7ba49e5 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ह्यान्डल"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index d1d7db8..dd3ebe4 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimaliseren"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Gebruikersnaam"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index d2a96d9..c3056ca 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ਹੈਂਡਲ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index cc2b2c3..56a6fb1 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Uchwyt"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 98a775e..b96caf2 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 5724061..a1ec3b5 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string>
     <string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string>
+    <string name="handle_text" msgid="1766582106752184456">"හැඬලය"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 12f022e..2f995e5 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Ročica"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index cdd8706..3d9bde4 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizo"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Pas"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Emërtimi"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index fef3792..b39fd04 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Handtag"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 2d10e20..f4d4cee 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Ncha"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 0eeeca7..6d050c2 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string>
     <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string>
+    <string name="handle_text" msgid="1766582106752184456">"ஹேண்டில்"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 2b105bdb..025e2e6 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Geri"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Herkese açık kullanıcı adı"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index a092531..97bb680 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 0330125..ce73bd5 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 6e4a768..511db6f 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string>
+    <string name="handle_text" msgid="1766582106752184456">"Xử lý"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index df911ed..16cbf12 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -84,8 +84,6 @@
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
-    <!-- no translation found for back_button_text (1469718707134137085) -->
-    <skip />
-    <!-- no translation found for handle_text (1766582106752184456) -->
-    <skip />
+    <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
+    <string name="handle_text" msgid="1766582106752184456">"处理"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index f484022..59d87a0 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -19,7 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="pip_phone_close" msgid="5783752637260411309">"Vala"</string>
     <string name="pip_phone_expand" msgid="2579292903468287504">"Nweba"</string>
-    <string name="pip_phone_settings" msgid="5468987116750491918">"Izilungiselelo"</string>
+    <string name="pip_phone_settings" msgid="5468987116750491918">"Amasethingi"</string>
     <string name="pip_phone_enter_split" msgid="7042877263880641911">"Faka ukuhlukanisa isikrini"</string>
     <string name="pip_menu_title" msgid="5393619322111827096">"Imenyu"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"U-<xliff:g id="NAME">%s</xliff:g> ungaphakathi kwesithombe esiphakathi kwesithombe"</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index e903897..f209521 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -33,6 +33,8 @@
     // This class is orientation-agnostic, so we compute both for later use
     public final float topTaskPercent;
     public final float leftTaskPercent;
+    public final float dividerWidthPercent;
+    public final float dividerHeightPercent;
     /**
      * If {@code true}, that means at the time of creation of this object, the
      * split-screened apps were vertically stacked. This is useful in scenarios like
@@ -62,8 +64,12 @@
             appsStackedVertically = false;
         }
 
-        leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
-        topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
+        float totalWidth = rightBottomBounds.right - leftTopBounds.left;
+        float totalHeight = rightBottomBounds.bottom - leftTopBounds.top;
+        leftTaskPercent = leftTopBounds.width() / totalWidth;
+        topTaskPercent = leftTopBounds.height() / totalHeight;
+        dividerWidthPercent = visualDividerBounds.width() / totalWidth;
+        dividerHeightPercent = visualDividerBounds.height() / totalHeight;
     }
 
     public SplitBounds(Parcel parcel) {
@@ -75,6 +81,8 @@
         appsStackedVertically = parcel.readBoolean();
         leftTopTaskId = parcel.readInt();
         rightBottomTaskId = parcel.readInt();
+        dividerWidthPercent = parcel.readInt();
+        dividerHeightPercent = parcel.readInt();
     }
 
     @Override
@@ -87,6 +95,8 @@
         parcel.writeBoolean(appsStackedVertically);
         parcel.writeInt(leftTopTaskId);
         parcel.writeInt(rightBottomTaskId);
+        parcel.writeFloat(dividerWidthPercent);
+        parcel.writeFloat(dividerHeightPercent);
     }
 
     @Override
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index 18b8ca9..77dfbee 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="4470785958457506021">"隨附裝置管理員"</string>
+    <string name="app_label" msgid="4470785958457506021">"隨附裝置管理工具"</string>
     <string name="confirmation_title" msgid="3785000297483688997">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
     <string name="chooser_title" msgid="2262294130493605839">"選擇要讓「<xliff:g id="APP_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index f79b328..5f1bb83 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -89,6 +89,11 @@
         var y: Float = 0f
 
         /**
+         * The current line of text being drawn, in a multi-line TextView.
+         */
+        var lineNo: Int = 0
+
+        /**
          * Mutable text size of the glyph in pixels.
          */
         var textSize: Float = 0f
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index d427a57..0448c81 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -244,7 +244,7 @@
                     canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
 
                     run.fontRuns.forEach { fontRun ->
-                        drawFontRun(canvas, run, fontRun, tmpPaint)
+                        drawFontRun(canvas, run, fontRun, lineNo, tmpPaint)
                     }
                 } finally {
                     canvas.restore()
@@ -349,7 +349,7 @@
     var glyphFilter: GlyphCallback? = null
 
     // Draws single font run.
-    private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) {
+    private fun drawFontRun(c: Canvas, line: Run, run: FontRun, lineNo: Int, paint: Paint) {
         var arrayIndex = 0
         val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress)
 
@@ -368,11 +368,13 @@
         tmpGlyph.font = font
         tmpGlyph.runStart = run.start
         tmpGlyph.runLength = run.end - run.start
+        tmpGlyph.lineNo = lineNo
 
         tmpPaintForGlyph.set(paint)
         var prevStart = run.start
 
         for (i in run.start until run.end) {
+            tmpGlyph.glyphIndex = i
             tmpGlyph.glyphId = line.glyphIds[i]
             tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
             tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 8457312..cf66ff6 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -40,6 +40,10 @@
         "tests/**/*.kt",
         "tests/**/*.java",
     ],
+    data: [
+        ":framework",
+        ":androidx.annotation_annotation",
+    ],
     static_libs: [
         "SystemUILintChecker",
         "junit",
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
index 4eeeb85..4b9aa13 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -32,7 +32,8 @@
 class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
 
     override fun getApplicableReferenceNames(): List<String> {
-        return mutableListOf("ALPHA_8", "RGB_565", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
+        return mutableListOf(
+            "ALPHA_8", "RGB_565", "ARGB_4444", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
     }
 
     override fun visitReference(
@@ -40,13 +41,12 @@
             reference: UReferenceExpression,
             referenced: PsiElement
     ) {
-
         val evaluator = context.evaluator
         if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
             context.report(
                     ISSUE,
                     referenced,
-                    context.getNameLocation(referenced),
+                    context.getNameLocation(reference),
                     "Replace software bitmap with `Config.HARDWARE`"
             )
         }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index d4c55c0..141dd05 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -18,185 +18,22 @@
 
 import com.android.annotations.NonNull
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile
+import java.io.File
 import org.intellij.lang.annotations.Language
 
 @Suppress("UnstableApiUsage")
 @NonNull
 private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented()
 
-internal val commonSettingsCode =
-    """
-public static float getFloat(ContentResolver cr, String name) { return 0.0f; }
-public static long getLong(ContentResolver cr, String name) {
-    return 0L;
-}
-public static int getInt(ContentResolver cr, String name) {
-    return 0;
-}
-public static String getString(ContentResolver cr, String name) {
-    return "";
-}
-public static float getFloat(ContentResolver cr, String name, float def) {
-    return 0.0f;
-}
-public static long getLong(ContentResolver cr, String name, long def) {
-    return 0L;
-}
-public static int getInt(ContentResolver cr, String name, int def) {
-    return 0;
-}
-public static String getString(ContentResolver cr, String name, String def) {
-    return "";
-}
-public static boolean putFloat(ContentResolver cr, String name, float value) {
-    return true;
-}
-public static boolean putLong(ContentResolver cr, String name, long value) {
-    return true;
-}
-public static boolean putInt(ContentResolver cr, String name, int value) {
-    return true;
-}
-public static boolean putFloat(ContentResolver cr, String name) {
-    return true;
-}
-public static boolean putString(ContentResolver cr, String name, String value) {
-    return true;
-}
-"""
-
 /*
  * This file contains stubs of framework APIs and System UI classes for testing purposes only. The
  * stubs are not used in the lint detectors themselves.
  */
 internal val androidStubs =
     arrayOf(
-        indentedJava(
-            """
-package android.app;
-
-public class ActivityManager {
-    public static int getCurrentUser() {}
-}
-"""
-        ),
-        indentedJava(
-            """
-package android.accounts;
-
-public class AccountManager {
-    public static AccountManager get(Context context) { return null; }
-}
-"""
-        ),
-        indentedJava(
-            """
-package android.os;
-import android.content.pm.UserInfo;
-import android.annotation.UserIdInt;
-
-public class UserManager {
-    public UserInfo getUserInfo(@UserIdInt int userId) {}
-}
-"""
-        ),
-        indentedJava("""
-package android.annotation;
-
-public @interface UserIdInt {}
-"""),
-        indentedJava("""
-package android.content.pm;
-
-public class UserInfo {}
-"""),
-        indentedJava("""
-package android.os;
-
-public class Looper {}
-"""),
-        indentedJava("""
-package android.os;
-
-public class Handler {}
-"""),
-        indentedJava("""
-package android.content;
-
-public class ServiceConnection {}
-"""),
-        indentedJava("""
-package android.os;
-
-public enum UserHandle {
-    ALL
-}
-"""),
-        indentedJava(
-            """
-package android.content;
-import android.os.UserHandle;
-import android.os.Handler;
-import android.os.Looper;
-import java.util.concurrent.Executor;
-
-public class Context {
-    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {}
-    public void registerReceiverAsUser(
-            BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
-            String broadcastPermission, Handler scheduler) {}
-    public void registerReceiverForAllUsers(
-            BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission,
-            Handler scheduler) {}
-    public void sendBroadcast(Intent intent) {}
-    public void sendBroadcast(Intent intent, String receiverPermission) {}
-    public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String permission) {}
-    public void bindService(Intent intent) {}
-    public void bindServiceAsUser(
-            Intent intent, ServiceConnection connection, int flags, UserHandle userHandle) {}
-    public void unbindService(ServiceConnection connection) {}
-    public Looper getMainLooper() { return null; }
-    public Executor getMainExecutor() { return null; }
-    public Handler getMainThreadHandler() { return null; }
-    public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { return null; }
-    public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
-}
-"""
-        ),
-        indentedJava(
-            """
-package android.app;
-import android.content.Context;
-
-public class Activity extends Context {}
-"""
-        ),
-        indentedJava(
-            """
-package android.graphics;
-
-public class Bitmap {
-    public enum Config {
-        ARGB_8888,
-        RGB_565,
-        HARDWARE
-    }
-    public static Bitmap createBitmap(int width, int height, Config config) {
-        return null;
-    }
-}
-"""
-        ),
-        indentedJava("""
-package android.content;
-
-public class BroadcastReceiver {}
-"""),
-        indentedJava("""
-package android.content;
-
-public class IntentFilter {}
-"""),
+        LibraryReferenceTestFile(File("framework.jar").canonicalFile),
+        LibraryReferenceTestFile(File("androidx.annotation_annotation.jar").canonicalFile),
         indentedJava(
             """
 package com.android.systemui.settings;
@@ -208,47 +45,4 @@
 }
 """
         ),
-        indentedJava(
-            """
-package androidx.annotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-@Retention(SOURCE)
-@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
-public @interface WorkerThread {
-}
-"""
-        ),
-        indentedJava(
-            """
-package android.provider;
-
-public class Settings {
-    public static final class Global {
-        public static final String UNLOCK_SOUND = "unlock_sound";
-        """ +
-                commonSettingsCode +
-                """
-    }
-    public static final class Secure {
-    """ +
-                commonSettingsCode +
-                """
-    }
-    public static final class System {
-    """ +
-                commonSettingsCode +
-                """
-    }
-}
-"""
-        ),
     )
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 090ddf8..c632636 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -51,12 +51,12 @@
             .run()
             .expect(
                 """
-                src/android/graphics/Bitmap.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
-                        ARGB_8888,
-                        ~~~~~~~~~
-                src/android/graphics/Bitmap.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
-                        RGB_565,
-                        ~~~~~~~
+                src/TestClass.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
+                      Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+                                                                  ~~~~~~~
+                src/TestClass.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
+                      Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+                                                                  ~~~~~~~~~
                 0 errors, 2 warnings
                 """
             )
@@ -67,7 +67,7 @@
         lint()
             .files(
                 TestFiles.java(
-                        """
+                    """
                     import android.graphics.Bitmap;
 
                     public class TestClass {
@@ -76,8 +76,7 @@
                         }
                     }
                 """
-                    )
-                    .indented(),
+                ),
                 *stubs
             )
             .issues(SoftwareBitmapDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
index 2183b38..3f93f07 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -3,9 +3,42 @@
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import java.io.File
+import org.junit.ClassRule
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.model.Statement
 
 @Suppress("UnstableApiUsage")
+@RunWith(JUnit4::class)
 abstract class SystemUILintDetectorTest : LintDetectorTest() {
+
+    companion object {
+        @ClassRule
+        @JvmField
+        val libraryChecker: LibraryExists =
+            LibraryExists("framework.jar", "androidx.annotation_annotation.jar")
+    }
+
+    class LibraryExists(vararg val libraryNames: String) : TestRule {
+        override fun apply(base: Statement, description: Description): Statement {
+            return object : Statement() {
+                override fun evaluate() {
+                    for (libName in libraryNames) {
+                        val libFile = File(libName)
+                        if (!libFile.canonicalFile.exists()) {
+                            throw Exception(
+                                "Could not find $libName in the test's working directory. " +
+                                    "File ${libFile.absolutePath} does not exist."
+                            )
+                        }
+                    }
+                    base.evaluate()
+                }
+            }
+        }
+    }
     /**
      * Customize the lint task to disable SDK usage completely. This ensures that running the tests
      * in Android Studio has the same result as running the tests in atest
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 1e74c3d..dabb43b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -14,6 +14,7 @@
 package com.android.systemui.plugins
 
 import android.content.res.Resources
+import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.view.View
 import com.android.systemui.plugins.annotations.ProvidesInterface
@@ -114,6 +115,17 @@
 
     /** Runs the battery animation (if any). */
     fun charge() { }
+
+    /** Move the clock, for example, if the notification tray appears in split-shade mode. */
+    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
+
+    /**
+     * Whether this clock has a custom position update animation. If true, the keyguard will call
+     * `onPositionUpdated` to notify the clock of a position update animation. If false, a default
+     * animation will be used (e.g. a simple translation).
+     */
+    val hasCustomPositionUpdatedAnimation
+        get() = false
 }
 
 /** Events that have specific data about the related face */
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 3ad7c8c..d64587d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -37,6 +37,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
+        android:clipChildren="false"
         android:visibility="gone" />
 
     <!-- Not quite optimal but needed to translate these items as a group. The
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index bbe4b6c..f5fc7ee5 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -672,7 +672,7 @@
     <string name="data_connection_no_internet" msgid="691058178914184544">"沒有網際網路連線"</string>
     <string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"開啟「<xliff:g id="ID_1">%s</xliff:g>」設定。"</string>
     <string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"編輯設定順序。"</string>
-    <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源按鈕選單"</string>
+    <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源鍵選單"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁,共 <xliff:g id="ID_2">%2$d</xliff:g> 頁"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"鎖定畫面"</string>
     <string name="thermal_shutdown_title" msgid="2702966892682930264">"手機先前過熱,因此關閉電源"</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 860a5da..1cf7c50 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -20,16 +20,15 @@
 import android.annotation.FloatRange
 import android.annotation.IntRange
 import android.annotation.SuppressLint
-import android.app.compat.ChangeIdStateCache.invalidate
 import android.content.Context
 import android.graphics.Canvas
+import android.graphics.Rect
 import android.text.Layout
 import android.text.TextUtils
 import android.text.format.DateFormat
 import android.util.AttributeSet
+import android.util.MathUtils
 import android.widget.TextView
-import com.android.internal.R.attr.contentDescription
-import com.android.internal.R.attr.format
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.Interpolators
@@ -39,6 +38,8 @@
 import java.util.Calendar
 import java.util.Locale
 import java.util.TimeZone
+import kotlin.math.max
+import kotlin.math.min
 
 /**
  * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
@@ -189,8 +190,13 @@
 
     override fun onDraw(canvas: Canvas) {
         lastDraw = getTimestamp()
-        // intentionally doesn't call super.onDraw here or else the text will be rendered twice
-        textAnimator?.draw(canvas)
+        // Use textAnimator to render text if animation is enabled.
+        // Otherwise default to using standard draw functions.
+        if (isAnimationEnabled) {
+            textAnimator?.draw(canvas)
+        } else {
+            super.onDraw(canvas)
+        }
     }
 
     override fun invalidate() {
@@ -311,7 +317,24 @@
         )
     }
 
-    private val glyphFilter: GlyphCallback? = null // Add text animation tweak here.
+    // The offset of each glyph from where it should be.
+    private var glyphOffsets = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
+
+    private var lastSeenAnimationProgress = 1.0f
+
+    // If the animation is being reversed, the target offset for each glyph for the "stop".
+    private var animationCancelStartPosition = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
+    private var animationCancelStopPosition = 0.0f
+
+    // Whether the currently playing animation needed a stop (and thus, is shortened).
+    private var currentAnimationNeededStop = false
+
+    private val glyphFilter: GlyphCallback = { positionedGlyph, _ ->
+        val offset = positionedGlyph.lineNo * DIGITS_PER_LINE + positionedGlyph.glyphIndex
+        if (offset < glyphOffsets.size) {
+            positionedGlyph.x += glyphOffsets[offset]
+        }
+    }
 
     /**
      * Set text style with an optional animation.
@@ -345,6 +368,9 @@
                 onAnimationEnd = onAnimationEnd
             )
             textAnimator?.glyphFilter = glyphFilter
+            if (color != null && !isAnimationEnabled) {
+                setTextColor(color)
+            }
         } else {
             // when the text animator is set, update its start values
             onTextAnimatorInitialized = Runnable {
@@ -359,6 +385,9 @@
                     onAnimationEnd = onAnimationEnd
                 )
                 textAnimator?.glyphFilter = glyphFilter
+                if (color != null && !isAnimationEnabled) {
+                    setTextColor(color)
+                }
             }
         }
     }
@@ -421,6 +450,124 @@
         pw.println("    time=$time")
     }
 
+    fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
+        // Do we need to cancel an in-flight animation?
+        // Need to also check against 0.0f here; we can sometimes get two calls with fraction == 0,
+        // which trips up the check otherwise.
+        if (lastSeenAnimationProgress != 1.0f &&
+                lastSeenAnimationProgress != 0.0f &&
+                fraction == 0.0f) {
+            // New animation, but need to stop the old one. Figure out where each glyph currently
+            // is in relation to the box position. After that, use the leading digit's current
+            // position as the stop target.
+            currentAnimationNeededStop = true
+
+            // We assume that the current glyph offsets would be relative to the "from" position.
+            val moveAmount = toRect.left - fromRect.left
+
+            // Remap the current glyph offsets to be relative to the new "end" position, and figure
+            // out the start/end positions for the stop animation.
+            for (i in 0 until NUM_DIGITS) {
+                glyphOffsets[i] = -moveAmount + glyphOffsets[i]
+                animationCancelStartPosition[i] = glyphOffsets[i]
+            }
+
+            // Use the leading digit's offset as the stop position.
+            if (toRect.left > fromRect.left) {
+                // It _was_ moving left
+                animationCancelStopPosition = glyphOffsets[0]
+            } else {
+                // It was moving right
+                animationCancelStopPosition = glyphOffsets[1]
+            }
+        }
+
+        // Is there a cancellation in progress?
+        if (currentAnimationNeededStop && fraction < ANIMATION_CANCELLATION_TIME) {
+            val animationStopProgress = MathUtils.constrainedMap(
+                    0.0f, 1.0f, 0.0f, ANIMATION_CANCELLATION_TIME, fraction
+            )
+
+            // One of the digits has already stopped.
+            val animationStopStep = 1.0f / (NUM_DIGITS - 1)
+
+            for (i in 0 until NUM_DIGITS) {
+                val stopAmount = if (toRect.left > fromRect.left) {
+                    // It was moving left (before flipping)
+                    MOVE_LEFT_DELAYS[i] * animationStopStep
+                } else {
+                    // It was moving right (before flipping)
+                    MOVE_RIGHT_DELAYS[i] * animationStopStep
+                }
+
+                // Leading digit stops immediately.
+                if (stopAmount == 0.0f) {
+                    glyphOffsets[i] = animationCancelStopPosition
+                } else {
+                    val actualStopAmount = MathUtils.constrainedMap(
+                            0.0f, 1.0f, 0.0f, stopAmount, animationStopProgress
+                    )
+                    val easedProgress = MOVE_INTERPOLATOR.getInterpolation(actualStopAmount)
+                    val glyphMoveAmount =
+                            animationCancelStopPosition - animationCancelStartPosition[i]
+                    glyphOffsets[i] =
+                            animationCancelStartPosition[i] + glyphMoveAmount * easedProgress
+                }
+            }
+        } else {
+            // Normal part of the animation.
+            // Do we need to remap the animation progress to take account of the cancellation?
+            val actualFraction = if (currentAnimationNeededStop) {
+                MathUtils.constrainedMap(
+                        0.0f, 1.0f, ANIMATION_CANCELLATION_TIME, 1.0f, fraction
+                )
+            } else {
+                fraction
+            }
+
+            val digitFractions = (0 until NUM_DIGITS).map {
+                // The delay for each digit, in terms of fraction (i.e. the digit should not move
+                // during 0.0 - 0.1).
+                val initialDelay = if (toRect.left > fromRect.left) {
+                    MOVE_RIGHT_DELAYS[it] * MOVE_DIGIT_STEP
+                } else {
+                    MOVE_LEFT_DELAYS[it] * MOVE_DIGIT_STEP
+                }
+
+                val f = MathUtils.constrainedMap(
+                        0.0f, 1.0f,
+                        initialDelay, initialDelay + AVAILABLE_ANIMATION_TIME,
+                        actualFraction
+                )
+                MOVE_INTERPOLATOR.getInterpolation(max(min(f, 1.0f), 0.0f))
+            }
+
+            // Was there an animation halt?
+            val moveAmount = if (currentAnimationNeededStop) {
+                // Only need to animate over the remaining space if the animation was aborted.
+                -animationCancelStopPosition
+            } else {
+                toRect.left.toFloat() - fromRect.left.toFloat()
+            }
+
+            for (i in 0 until NUM_DIGITS) {
+                glyphOffsets[i] = -moveAmount + (moveAmount * digitFractions[i])
+            }
+        }
+
+        invalidate()
+
+        if (fraction == 1.0f) {
+            // Reset
+            currentAnimationNeededStop = false
+        }
+
+        lastSeenAnimationProgress = fraction
+
+        // Ensure that the actual clock container is always in the "end" position.
+        this.setLeftTopRightBottom(toRect.left, toRect.top, toRect.right, toRect.bottom)
+    }
+
     // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
     // This is an optimization to ensure we only recompute the patterns when the inputs change.
     private object Patterns {
@@ -458,5 +605,36 @@
         private const val APPEAR_ANIM_DURATION: Long = 350
         private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
         private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
+
+        // Constants for the animation
+        private val MOVE_INTERPOLATOR = Interpolators.STANDARD
+
+        // Calculate the positions of all of the digits...
+        // Offset each digit by, say, 0.1
+        // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should
+        // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3
+        // from 0.3 - 1.0.
+        private const val NUM_DIGITS = 4
+        private const val DIGITS_PER_LINE = 2
+
+        // How much of "fraction" to spend on canceling the animation, if needed
+        private const val ANIMATION_CANCELLATION_TIME = 0.4f
+
+        // Delays. Each digit's animation should have a slight delay, so we get a nice
+        // "stepping" effect. When moving right, the second digit of the hour should move first.
+        // When moving left, the first digit of the hour should move first. The lists encode
+        // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied
+        // by delayMultiplier.
+        private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3)
+        private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2)
+
+        // How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
+        // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
+        // before moving).
+        private const val MOVE_DIGIT_STEP = 0.1f
+
+        // Total available transition time for each digit, taking into account the step. If step is
+        // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
+        private val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index e3c21cc..cd27263 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,7 +21,6 @@
 import android.os.UserHandle
 import android.provider.Settings
 import android.util.Log
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.internal.annotations.Keep
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
@@ -31,7 +30,6 @@
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.shared.plugins.PluginManager
 import com.google.gson.Gson
-import javax.inject.Inject
 
 private val TAG = ClockRegistry::class.simpleName
 private const val DEBUG = true
@@ -43,13 +41,6 @@
     val handler: Handler,
     defaultClockProvider: ClockProvider
 ) {
-    @Inject constructor(
-        context: Context,
-        pluginManager: PluginManager,
-        @Main handler: Handler,
-        defaultClockProvider: DefaultClockProvider
-    ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
-
     // Usually this would be a typealias, but a SAM provides better java interop
     fun interface ClockChangeListener {
         fun onClockChanged()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index b887951..6fd61da 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -16,6 +16,7 @@
 import android.content.Context
 import android.content.res.Resources
 import android.graphics.Color
+import android.graphics.Rect
 import android.icu.text.NumberFormat
 import android.util.TypedValue
 import android.view.LayoutInflater
@@ -130,6 +131,10 @@
             lp.topMargin = (-0.5f * view.bottom).toInt()
             view.setLayoutParams(lp)
         }
+
+        fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
+            view.moveForSplitShade(fromRect, toRect, fraction)
+        }
     }
 
     inner class DefaultClockEvents : ClockEvents {
@@ -209,6 +214,13 @@
                 clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
             }
         }
+
+        override fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {
+            largeClock.moveForSplitShade(fromRect, toRect, fraction)
+        }
+
+        override val hasCustomPositionUpdatedAnimation: Boolean
+            get() = true
     }
 
     private class AnimationState(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 72f8b7b..40c8774 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -1,13 +1,16 @@
 package com.android.systemui.shared.recents.utilities;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.view.Surface;
 
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.wm.shell.util.SplitBounds;
 
 /**
  * Utility class to position the thumbnail in the TaskView
@@ -16,10 +19,26 @@
 
     public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
 
+    /**
+     * Specifies that a stage is positioned at the top half of the screen if
+     * in portrait mode or at the left half of the screen if in landscape mode.
+     * TODO(b/254378592): Remove after consolidation
+     */
+    public static final int STAGE_POSITION_TOP_OR_LEFT = 0;
+
+    /**
+     * Specifies that a stage is positioned at the bottom half of the screen if
+     * in portrait mode or at the right half of the screen if in landscape mode.
+     * TODO(b/254378592): Remove after consolidation
+     */
+    public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
     // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
     private final RectF mClippedInsets = new RectF();
     private final Matrix mMatrix = new Matrix();
     private boolean mIsOrientationChanged;
+    private SplitBounds mSplitBounds;
+    private int mDesiredStagePosition;
 
     public Matrix getMatrix() {
         return mMatrix;
@@ -33,6 +52,11 @@
         return mIsOrientationChanged;
     }
 
+    public void setSplitBounds(SplitBounds splitBounds, int desiredStagePosition) {
+        mSplitBounds = splitBounds;
+        mDesiredStagePosition = desiredStagePosition;
+    }
+
     /**
      * Updates the matrix based on the provided parameters
      */
@@ -42,10 +66,19 @@
         boolean isRotated = false;
         boolean isOrientationDifferent;
 
+        float fullscreenTaskWidth = screenWidthPx;
+        if (mSplitBounds != null && !mSplitBounds.appsStackedVertically) {
+            // For landscape, scale the width
+            float taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
+                    ? mSplitBounds.leftTaskPercent
+                    : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
+            // Scale landscape width to that of actual screen
+            fullscreenTaskWidth = screenWidthPx * taskPercent;
+        }
         int thumbnailRotation = thumbnailData.rotation;
         int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
         RectF thumbnailClipHint = new RectF();
-        float canvasScreenRatio = canvasWidth / (float) screenWidthPx;
+        float canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
         float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
         thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
 
@@ -180,7 +213,7 @@
      * portrait or vice versa, {@code false} otherwise
      */
     private boolean isOrientationChange(int deltaRotation) {
-        return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+        return deltaRotation == ROTATION_90 || deltaRotation == ROTATION_270;
     }
 
     private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
@@ -189,13 +222,13 @@
 
         mMatrix.setRotate(90 * deltaRotate);
         switch (deltaRotate) { /* Counter-clockwise */
-            case Surface.ROTATION_90:
+            case ROTATION_90:
                 translateX = thumbnailPosition.height();
                 break;
-            case Surface.ROTATION_270:
+            case ROTATION_270:
                 translateY = thumbnailPosition.width();
                 break;
-            case Surface.ROTATION_180:
+            case ROTATION_180:
                 translateX = thumbnailPosition.width();
                 translateY = thumbnailPosition.height();
                 break;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 202134b..35eecdf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.ClockRegistry;
@@ -404,5 +405,10 @@
             clock.dump(pw);
         }
     }
+
+    /** Gets the animations for the current clock. */
+    public ClockAnimations getClockAnimations() {
+        return getClock().getAnimations();
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index e9f06ed..7849747 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,7 @@
 import android.util.Slog;
 
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -232,4 +233,9 @@
             mView.setClipBounds(null);
         }
     }
+
+    /** Gets the animations for the current clock. */
+    public ClockAnimations getClockAnimations() {
+        return mKeyguardClockSwitchController.getClockAnimations();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
similarity index 86%
rename from packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
rename to packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
index c4be1ba535..72a44bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
@@ -21,9 +21,14 @@
 import dagger.Module;
 import dagger.Provides;
 
-/** Dagger Module for clock package. */
+/**
+ * Dagger Module for clock package.
+ *
+ * @deprecated Migrate to ClockRegistry
+ */
 @Module
-public abstract class ClockModule {
+@Deprecated
+public abstract class ClockInfoModule {
 
     /** */
     @Provides
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
new file mode 100644
index 0000000..f43f559
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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.keyguard.dagger;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.shared.clocks.DefaultClockProvider;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger Module for clocks. */
+@Module
+public abstract class ClockRegistryModule {
+    /** Provide the ClockRegistry as a singleton so that it is not instantiated more than once. */
+    @Provides
+    @SysUISingleton
+    public static ClockRegistry getClockRegistry(
+            @Application Context context,
+            PluginManager pluginManager,
+            @Main Handler handler,
+            DefaultClockProvider defaultClockProvider) {
+        return new ClockRegistry(context, pluginManager, handler, defaultClockProvider);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index d7638d6..7e31626 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -23,7 +23,8 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.clock.ClockModule;
+import com.android.keyguard.clock.ClockInfoModule;
+import com.android.keyguard.dagger.ClockRegistryModule;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
@@ -120,7 +121,8 @@
             BiometricsModule.class,
             BouncerViewModule.class,
             ClipboardOverlayModule.class,
-            ClockModule.class,
+            ClockInfoModule.class,
+            ClockRegistryModule.class,
             CoroutinesModule.class,
             DreamModule.class,
             ControlsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 00a9ddf..9415d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -143,6 +143,12 @@
     // TODO(b/254513102): Tracking Bug
     public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211);
 
+    /**
+     * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
+     * the digits when the clock moves.
+     */
+    public static final UnreleasedFlag STEP_CLOCK_ANIMATION = new UnreleasedFlag(212);
+
     /***************************************/
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
@@ -278,7 +284,7 @@
     public static final ReleasedFlag ROUNDED_BOX_RIPPLE = new ReleasedFlag(1002);
 
     // TODO(b/254512525): Tracking Bug
-    public static final UnreleasedFlag REFACTORED_DOCK_SETUP = new UnreleasedFlag(1003, true);
+    public static final ReleasedFlag REFACTORED_DOCK_SETUP = new ReleasedFlag(1003);
 
     // 1100 - windowing
     @Keep
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c1afd59..5423683 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -79,7 +79,10 @@
 import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.transition.ChangeBounds;
+import android.transition.Transition;
 import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.transition.TransitionValues;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.MathUtils;
@@ -1667,9 +1670,40 @@
                     // horizontally properly.
                     transition.excludeTarget(R.id.status_view_media_container, true);
                 }
+
                 transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
                 transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                TransitionManager.beginDelayedTransition(mNotificationContainerParent, transition);
+
+                boolean customClockAnimation =
+                            mKeyguardStatusViewController.getClockAnimations() != null
+                            && mKeyguardStatusViewController.getClockAnimations()
+                                    .getHasCustomPositionUpdatedAnimation();
+
+                if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
+                    // Find the clock, so we can exclude it from this transition.
+                    FrameLayout clockContainerView =
+                            mView.findViewById(R.id.lockscreen_clock_view_large);
+                    View clockView = clockContainerView.getChildAt(0);
+
+                    transition.excludeTarget(clockView, /* exclude= */ true);
+
+                    TransitionSet set = new TransitionSet();
+                    set.addTransition(transition);
+
+                    SplitShadeTransitionAdapter adapter =
+                            new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
+
+                    // Use linear here, so the actual clock can pick its own interpolator.
+                    adapter.setInterpolator(Interpolators.LINEAR);
+                    adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                    adapter.addTarget(clockView);
+                    set.addTransition(adapter);
+
+                    TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+                } else {
+                    TransitionManager.beginDelayedTransition(
+                            mNotificationContainerParent, transition);
+                }
             }
 
             constraintSet.applyTo(mNotificationContainerParent);
@@ -4166,8 +4200,8 @@
     /**
      * Sets the dozing state.
      *
-     * @param dozing              {@code true} when dozing.
-     * @param animate             if transition should be animated.
+     * @param dozing  {@code true} when dozing.
+     * @param animate if transition should be animated.
      */
     public void setDozing(boolean dozing, boolean animate) {
         if (dozing == mDozing) return;
@@ -4307,35 +4341,35 @@
     /**
      * Starts fold to AOD animation.
      *
-     * @param startAction invoked when the animation starts.
-     * @param endAction invoked when the animation finishes, also if it was cancelled.
+     * @param startAction  invoked when the animation starts.
+     * @param endAction    invoked when the animation finishes, also if it was cancelled.
      * @param cancelAction invoked when the animation is cancelled, before endAction.
      */
     public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
             Runnable cancelAction) {
         mView.animate()
-            .translationX(0)
-            .alpha(1f)
-            .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
-            .setInterpolator(EMPHASIZED_DECELERATE)
-            .setListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    startAction.run();
-                }
+                .translationX(0)
+                .alpha(1f)
+                .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+                .setInterpolator(EMPHASIZED_DECELERATE)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        startAction.run();
+                    }
 
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    cancelAction.run();
-                }
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        cancelAction.run();
+                    }
 
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    endAction.run();
-                }
-            }).setUpdateListener(anim -> {
-                mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
-            }).start();
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        endAction.run();
+                    }
+                }).setUpdateListener(anim -> {
+                    mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+                }).start();
     }
 
     /**
@@ -4695,8 +4729,10 @@
     /**
      * Maybe vibrate as panel is opened.
      *
-     * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
-     * being opened programmatically (such as by the open panel gesture), we always play haptic.
+     * @param openingWithTouch Whether the panel is being opened with touch. If the panel is
+     *                         instead
+     *                         being opened programmatically (such as by the open panel gesture), we
+     *                         always play haptic.
      */
     private void maybeVibrateOnOpening(boolean openingWithTouch) {
         if (mVibrateOnOpening) {
@@ -4861,10 +4897,12 @@
         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         animator.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
+
             @Override
             public void onAnimationCancel(Animator animation) {
                 mCancelled = true;
             }
+
             @Override
             public void onAnimationEnd(Animator animation) {
                 mIsSpringBackAnimation = false;
@@ -4912,7 +4950,7 @@
         if (isNaN(h)) {
             Log.wtf(TAG, "ExpandedHeight set to NaN");
         }
-        mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+        mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
             if (mExpandLatencyTracking && h != 0f) {
                 DejankUtils.postAfterTraversal(
                         () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
@@ -5103,7 +5141,7 @@
     /**
      * Create an animator that can also overshoot
      *
-     * @param targetHeight the target height
+     * @param targetHeight    the target height
      * @param overshootAmount the amount of overshoot desired
      */
     private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
@@ -5900,7 +5938,7 @@
     public final class TouchHandler implements View.OnTouchListener {
         private long mLastTouchDownTime = -1L;
 
-        /** @see ViewGroup#onInterceptTouchEvent(MotionEvent)  */
+        /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
         public boolean onInterceptTouchEvent(MotionEvent event) {
             if (SPEW_LOGCAT) {
                 Log.v(TAG,
@@ -6099,7 +6137,7 @@
                 mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
                 return false;
             }
-            if (mTouchDisabled  && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+            if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
                 mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
                 return false;
             }
@@ -6256,4 +6294,54 @@
             loadDimens();
         }
     }
+
+    static class SplitShadeTransitionAdapter extends Transition {
+        private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
+        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
+
+        private final KeyguardStatusViewController mController;
+
+        SplitShadeTransitionAdapter(KeyguardStatusViewController controller) {
+            mController = controller;
+        }
+
+        private void captureValues(TransitionValues transitionValues) {
+            Rect boundsRect = new Rect();
+            boundsRect.left = transitionValues.view.getLeft();
+            boundsRect.top = transitionValues.view.getTop();
+            boundsRect.right = transitionValues.view.getRight();
+            boundsRect.bottom = transitionValues.view.getBottom();
+            transitionValues.values.put(PROP_BOUNDS, boundsRect);
+        }
+
+        @Override
+        public void captureEndValues(TransitionValues transitionValues) {
+            captureValues(transitionValues);
+        }
+
+        @Override
+        public void captureStartValues(TransitionValues transitionValues) {
+            captureValues(transitionValues);
+        }
+
+        @Override
+        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+                TransitionValues endValues) {
+            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+            Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
+            Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
+
+            anim.addUpdateListener(
+                    animation -> mController.getClockAnimations().onPositionUpdated(
+                            from, to, animation.getAnimatedFraction()));
+
+            return anim;
+        }
+
+        @Override
+        public String[] getTransitionProperties() {
+            return TRANSITION_PROPERTIES;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8f3eb4f..8a31ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -18,6 +18,8 @@
 import android.app.Notification
 import android.app.Notification.GROUP_ALERT_SUMMARY
 import android.util.ArrayMap
+import android.util.ArraySet
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -70,6 +72,7 @@
     @Main private val mExecutor: DelayableExecutor,
 ) : Coordinator {
     private val mEntriesBindingUntil = ArrayMap<String, Long>()
+    private val mEntriesUpdateTimes = ArrayMap<String, Long>()
     private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
     private lateinit var mNotifPipeline: NotifPipeline
     private var mNow: Long = -1
@@ -264,6 +267,9 @@
         }
         // After this method runs, all posted entries should have been handled (or skipped).
         mPostedEntries.clear()
+
+        // Also take this opportunity to clean up any stale entry update times
+        cleanUpEntryUpdateTimes()
     }
 
     /**
@@ -378,6 +384,9 @@
                 isAlerting = false,
                 isBinding = false,
             )
+
+            // Record the last updated time for this key
+            setUpdateTime(entry, mSystemClock.currentTimeMillis())
         }
 
         /**
@@ -419,6 +428,9 @@
                     cancelHeadsUpBind(posted.entry)
                 }
             }
+
+            // Update last updated time for this entry
+            setUpdateTime(entry, mSystemClock.currentTimeMillis())
         }
 
         /**
@@ -426,6 +438,7 @@
          */
         override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
             mPostedEntries.remove(entry.key)
+            mEntriesUpdateTimes.remove(entry.key)
             cancelHeadsUpBind(entry)
             val entryKey = entry.key
             if (mHeadsUpManager.isAlerting(entryKey)) {
@@ -454,7 +467,12 @@
             // never) in mPostedEntries to need to alert, we need to check every notification
             // known to the pipeline.
             for (entry in mNotifPipeline.allNotifs) {
-                // The only entries we can consider alerting for here are entries that have never
+                // Only consider entries that are recent enough, since we want to apply a fairly
+                // strict threshold for when an entry should be updated via only ranking and not an
+                // app-provided notification update.
+                if (!isNewEnoughForRankingUpdate(entry)) continue
+
+                // The only entries we consider alerting for here are entries that have never
                 // interrupted and that now say they should heads up; if they've alerted in the
                 // past, we don't want to incorrectly alert a second time if there wasn't an
                 // explicit notification update.
@@ -486,6 +504,41 @@
                 (entry.sbn.notification.flags and Notification.FLAG_ONLY_ALERT_ONCE) == 0)
     }
 
+    /**
+     * Sets the updated time for the given entry to the specified time.
+     */
+    @VisibleForTesting
+    fun setUpdateTime(entry: NotificationEntry, time: Long) {
+        mEntriesUpdateTimes[entry.key] = time
+    }
+
+    /**
+     * Checks whether the entry is new enough to be updated via ranking update.
+     * We want to avoid updating an entry too long after it was originally posted/updated when we're
+     * only reacting to a ranking change, as relevant ranking updates are expected to come in
+     * fairly soon after the posting of a notification.
+     */
+    private fun isNewEnoughForRankingUpdate(entry: NotificationEntry): Boolean {
+        // If we don't have an update time for this key, default to "too old"
+        if (!mEntriesUpdateTimes.containsKey(entry.key)) return false
+
+        val updateTime = mEntriesUpdateTimes[entry.key] ?: return false
+        return (mSystemClock.currentTimeMillis() - updateTime) <= MAX_RANKING_UPDATE_DELAY_MS
+    }
+
+    private fun cleanUpEntryUpdateTimes() {
+        // Because we won't update entries that are older than this amount of time anyway, clean
+        // up any entries that are too old to notify.
+        val toRemove = ArraySet<String>()
+        for ((key, updateTime) in mEntriesUpdateTimes) {
+            if (updateTime == null ||
+                    (mSystemClock.currentTimeMillis() - updateTime) > MAX_RANKING_UPDATE_DELAY_MS) {
+                toRemove.add(key)
+            }
+        }
+        mEntriesUpdateTimes.removeAll(toRemove)
+    }
+
     /** When an action is pressed on a notification, end HeadsUp lifetime extension. */
     private val mActionPressListener = Consumer<NotificationEntry> { entry ->
         if (mNotifsExtendingLifetime.contains(entry)) {
@@ -597,6 +650,9 @@
     companion object {
         private const val TAG = "HeadsUpCoordinator"
         private const val BIND_TIMEOUT = 1000L
+
+        // This value is set to match MAX_SOUND_DELAY_MS in NotificationRecord.
+        private const val MAX_RANKING_UPDATE_DELAY_MS: Long = 2000
     }
 
     data class PostedEntry(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index bb03a47..627d738 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -29,6 +29,7 @@
 
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -45,6 +46,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -262,6 +264,19 @@
         verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
     }
 
+    @Test
+    public void testGetClockAnimationsForwardsToClock() {
+        ClockController mockClockController = mock(ClockController.class);
+        ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
+        when(mClockEventController.getClock()).thenReturn(mockClockController);
+        when(mockClockController.getAnimations()).thenReturn(mockClockAnimations);
+
+        Rect r1 = new Rect(1, 2, 3, 4);
+        Rect r2 = new Rect(5, 6, 7, 8);
+        mController.getClockAnimations().onPositionUpdated(r1, r2, 0.2f);
+        verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.2f);
+    }
+
     private void verifyAttachment(VerificationMode times) {
         verify(mClockRegistry, times).registerClockChangeListener(
                 any(ClockRegistry.ClockChangeListener.class));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 4dcaa7c..c94c97c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,12 +16,16 @@
 
 package com.android.keyguard;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.graphics.Rect;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -108,4 +112,16 @@
         configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
         verify(mKeyguardClockSwitchController).onLocaleListChanged();
     }
+
+    @Test
+    public void getClockAnimations_forwardsToClockSwitch() {
+        ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
+        when(mKeyguardClockSwitchController.getClockAnimations()).thenReturn(mockClockAnimations);
+
+        Rect r1 = new Rect(1, 2, 3, 4);
+        Rect r2 = new Rect(5, 6, 7, 8);
+        mController.getClockAnimations().onPositionUpdated(r1, r2, 0.3f);
+
+        verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.3f);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index eb34561..cc45cf88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.TextAnimator
+import com.android.systemui.util.mockito.any
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -55,7 +56,7 @@
         clockView.animateAppearOnLockscreen()
         clockView.measure(50, 50)
 
-        verify(mockTextAnimator).glyphFilter = null
+        verify(mockTextAnimator).glyphFilter = any()
         verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
         verifyNoMoreInteractions(mockTextAnimator)
     }
@@ -66,7 +67,7 @@
         clockView.measure(50, 50)
         clockView.animateAppearOnLockscreen()
 
-        verify(mockTextAnimator, times(2)).glyphFilter = null
+        verify(mockTextAnimator, times(2)).glyphFilter = any()
         verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
         verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
         verifyNoMoreInteractions(mockTextAnimator)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 340bc96..3ff7639 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -674,7 +674,9 @@
     @Test
     fun testOnRankingApplied_newEntryShouldAlert() {
         // GIVEN that mEntry has never interrupted in the past, and now should
+        // and is new enough to do so
         assertFalse(mEntry.hasInterrupted())
+        mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
         setShouldHeadsUp(mEntry)
         whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
 
@@ -690,8 +692,9 @@
 
     @Test
     fun testOnRankingApplied_alreadyAlertedEntryShouldNotAlertAgain() {
-        // GIVEN that mEntry has alerted in the past
+        // GIVEN that mEntry has alerted in the past, even if it's new
         mEntry.setInterruption()
+        mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
         setShouldHeadsUp(mEntry)
         whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
 
@@ -725,6 +728,27 @@
         verify(mHeadsUpManager).showNotification(mEntry)
     }
 
+    @Test
+    fun testOnRankingApplied_entryUpdatedButTooOld() {
+        // GIVEN that mEntry is added in a state where it should not HUN
+        setShouldHeadsUp(mEntry, false)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // and it was actually added 10s ago
+        mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis() - 10000)
+
+        // WHEN it is updated to HUN and then a ranking update occurs
+        setShouldHeadsUp(mEntry)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN the notification is never bound or shown
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+    }
+
     private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
         whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
         whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fd1fdce..a55b118 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -61,6 +61,7 @@
 import static android.content.pm.PackageManager.FEATURE_TELECOM;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.content.pm.PackageManager.MATCH_ALL;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -10633,10 +10634,18 @@
         private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
         ArrayMap<Pair<ComponentName, Integer>, NotificationListenerFilter>
                 mRequestedNotificationListeners = new ArrayMap<>();
+        private final boolean mIsHeadlessSystemUserMode;
 
         public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
                 IPackageManager pm) {
+            this(context, lock, userProfiles, pm, UserManager.isHeadlessSystemUserMode());
+        }
+
+        @VisibleForTesting
+        public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
+                IPackageManager pm, boolean isHeadlessSystemUserMode) {
             super(context, lock, userProfiles, pm);
+            this.mIsHeadlessSystemUserMode = isHeadlessSystemUserMode;
         }
 
         @Override
@@ -10661,10 +10670,16 @@
                     if (TextUtils.isEmpty(listeners[i])) {
                         continue;
                     }
+                    int packageQueryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+                    // In the headless system user mode, packages might not be installed for the
+                    // system user. Match packages for any user since apps can be installed only for
+                    // non-system users and would be considering uninstalled for the system user.
+                    if (mIsHeadlessSystemUserMode) {
+                        packageQueryFlags += MATCH_ANY_USER;
+                    }
                     ArraySet<ComponentName> approvedListeners =
-                            this.queryPackageForServices(listeners[i],
-                                    MATCH_DIRECT_BOOT_AWARE
-                                            | MATCH_DIRECT_BOOT_UNAWARE, USER_SYSTEM);
+                            this.queryPackageForServices(listeners[i], packageQueryFlags,
+                                    USER_SYSTEM);
                     for (int k = 0; k < approvedListeners.size(); k++) {
                         ComponentName cn = approvedListeners.valueAt(k);
                         addDefaultComponentOrPackage(cn.flattenToString());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index c5131c8..101a5cb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -30,9 +31,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.intThat;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -49,6 +52,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.pm.VersionedPackage;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerFilter;
@@ -69,6 +73,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.internal.util.reflection.FieldSetter;
@@ -77,6 +82,7 @@
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
 import java.util.List;
 
 public class NotificationListenersTest extends UiServiceTestCase {
@@ -85,6 +91,8 @@
     private PackageManager mPm;
     @Mock
     private IPackageManager miPm;
+    @Mock
+    private Resources mResources;
 
     @Mock
     NotificationManagerService mNm;
@@ -96,7 +104,8 @@
 
     private ComponentName mCn1 = new ComponentName("pkg", "pkg.cmp");
     private ComponentName mCn2 = new ComponentName("pkg2", "pkg2.cmp2");
-
+    private ComponentName mUninstalledComponent = new ComponentName("pkg3",
+            "pkg3.NotificationListenerService");
 
     @Before
     public void setUp() throws Exception {
@@ -111,7 +120,7 @@
 
     @Test
     public void testReadExtraTag() throws Exception {
-        String xml = "<" + TAG_REQUESTED_LISTENERS+ ">"
+        String xml = "<" + TAG_REQUESTED_LISTENERS + ">"
                 + "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
                 + "<allowed types=\"7\" />"
                 + "</listener>"
@@ -131,11 +140,55 @@
     }
 
     @Test
+    public void loadDefaultsFromConfig_forHeadlessSystemUser_loadUninstalled() throws Exception {
+        // setup with headless system user mode
+        mListeners = spy(mNm.new NotificationListeners(
+                mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm,
+                /* isHeadlessSystemUserMode= */ true));
+        mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent);
+
+        mListeners.loadDefaultsFromConfig();
+
+        assertThat(mListeners.getDefaultComponents()).contains(mUninstalledComponent);
+    }
+
+    @Test
+    public void loadDefaultsFromConfig_forNonHeadlessSystemUser_ignoreUninstalled()
+            throws Exception {
+        // setup without headless system user mode
+        mListeners = spy(mNm.new NotificationListeners(
+                mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm,
+                /* isHeadlessSystemUserMode= */ false));
+        mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent);
+
+        mListeners.loadDefaultsFromConfig();
+
+        assertThat(mListeners.getDefaultComponents()).doesNotContain(mUninstalledComponent);
+    }
+
+    private void mockDefaultListenerConfigForUninstalledComponent(ComponentName componentName) {
+        ArraySet<ComponentName> components = new ArraySet<>(Arrays.asList(componentName));
+        when(mResources
+                .getString(
+                        com.android.internal.R.string.config_defaultListenerAccessPackages))
+                .thenReturn(componentName.getPackageName());
+        when(mContext.getResources()).thenReturn(mResources);
+        doReturn(components).when(mListeners).queryPackageForServices(
+                eq(componentName.getPackageName()),
+                intThat(hasIntBitFlag(MATCH_ANY_USER)),
+                anyInt());
+    }
+
+    public static ArgumentMatcher<Integer> hasIntBitFlag(int flag) {
+        return arg -> arg != null && ((arg & flag) == flag);
+    }
+
+    @Test
     public void testWriteExtraTag() throws Exception {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
         VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[]{a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
deleted file mode 100644
index d765042..0000000
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
+++ /dev/null
@@ -1,551 +0,0 @@
-/*
- * 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
- */
-
-package com.android.server.notification;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.app.RemoteInput;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.StyleSpan;
-import android.util.Pair;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.UiServiceTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NotificationTest extends UiServiceTestCase {
-
-    @Mock
-    ActivityManager mAm;
-
-    @Mock
-    Resources mResources;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testDoesNotStripsExtenders() {
-        Notification.Builder nb = new Notification.Builder(mContext, "channel");
-        nb.extend(new Notification.CarExtender().setColor(Color.RED));
-        nb.extend(new Notification.TvExtender().setChannelId("different channel"));
-        nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
-        Notification before = nb.build();
-        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
-
-        assertTrue(before == after);
-
-        assertEquals("different channel", new Notification.TvExtender(before).getChannelId());
-        assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
-        assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
-    }
-
-    @Test
-    public void testStyleChangeVisiblyDifferent_noStyles() {
-        Notification.Builder n1 = new Notification.Builder(mContext, "test");
-        Notification.Builder n2 = new Notification.Builder(mContext, "test");
-
-        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testStyleChangeVisiblyDifferent_noStyleToStyle() {
-        Notification.Builder n1 = new Notification.Builder(mContext, "test");
-        Notification.Builder n2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle());
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testStyleChangeVisiblyDifferent_styleToNoStyle() {
-        Notification.Builder n2 = new Notification.Builder(mContext, "test");
-        Notification.Builder n1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle());
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testStyleChangeVisiblyDifferent_changeStyle() {
-        Notification.Builder n1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.InboxStyle());
-        Notification.Builder n2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle());
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testInboxTextChange() {
-        Notification.Builder nInbox1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.InboxStyle().addLine("a").addLine("b"));
-        Notification.Builder nInbox2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.InboxStyle().addLine("b").addLine("c"));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2));
-    }
-
-    @Test
-    public void testBigTextTextChange() {
-        Notification.Builder nBigText1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle().bigText("something"));
-        Notification.Builder nBigText2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigTextStyle().bigText("else"));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2));
-    }
-
-    @Test
-    public void testBigPictureChange() {
-        Bitmap bitA = mock(Bitmap.class);
-        when(bitA.getGenerationId()).thenReturn(100);
-        Bitmap bitB = mock(Bitmap.class);
-        when(bitB.getGenerationId()).thenReturn(200);
-
-        Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigPictureStyle().bigPicture(bitA));
-        Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.BigPictureStyle().bigPicture(bitB));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2));
-    }
-
-    @Test
-    public void testMessagingChange_text() {
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class))));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class)))
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "b", 100, mock(Person.class)))
-                );
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testMessagingChange_data() {
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class))
-                                .setData("text", mock(Uri.class))));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class))));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testMessagingChange_sender() {
-        Person a = mock(Person.class);
-        when(a.getName()).thenReturn("A");
-        Person b = mock(Person.class);
-        when(b.getName()).thenReturn("b");
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testMessagingChange_key() {
-        Person a = mock(Person.class);
-        when(a.getKey()).thenReturn("A");
-        Person b = mock(Person.class);
-        when(b.getKey()).thenReturn("b");
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
-
-        assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testMessagingChange_ignoreTimeChange() {
-        Notification.Builder nM1 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 100, mock(Person.class))));
-        Notification.Builder nM2 = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle("")
-                        .addMessage(new Notification.MessagingStyle.Message(
-                                "a", 1000, mock(Person.class)))
-                );
-
-        assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
-    }
-
-    @Test
-    public void testRemoteViews_nullChange() {
-        Notification.Builder n1 = new Notification.Builder(mContext, "test")
-                .setContent(mock(RemoteViews.class));
-        Notification.Builder n2 = new Notification.Builder(mContext, "test");
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test");
-        n2 = new Notification.Builder(mContext, "test")
-                .setContent(mock(RemoteViews.class));
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test")
-                .setCustomBigContentView(mock(RemoteViews.class));
-        n2 = new Notification.Builder(mContext, "test");
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test");
-        n2 = new Notification.Builder(mContext, "test")
-                .setCustomBigContentView(mock(RemoteViews.class));
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test");
-        n2 = new Notification.Builder(mContext, "test");
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testRemoteViews_layoutChange() {
-        RemoteViews a = mock(RemoteViews.class);
-        when(a.getLayoutId()).thenReturn(234);
-        RemoteViews b = mock(RemoteViews.class);
-        when(b.getLayoutId()).thenReturn(189);
-
-        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
-        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testRemoteViews_layoutSame() {
-        RemoteViews a = mock(RemoteViews.class);
-        when(a.getLayoutId()).thenReturn(234);
-        RemoteViews b = mock(RemoteViews.class);
-        when(b.getLayoutId()).thenReturn(234);
-
-        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
-        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testRemoteViews_sequenceChange() {
-        RemoteViews a = mock(RemoteViews.class);
-        when(a.getLayoutId()).thenReturn(234);
-        when(a.getSequenceNumber()).thenReturn(1);
-        RemoteViews b = mock(RemoteViews.class);
-        when(b.getLayoutId()).thenReturn(234);
-        when(b.getSequenceNumber()).thenReturn(2);
-
-        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
-        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
-        assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testRemoteViews_sequenceSame() {
-        RemoteViews a = mock(RemoteViews.class);
-        when(a.getLayoutId()).thenReturn(234);
-        when(a.getSequenceNumber()).thenReturn(1);
-        RemoteViews b = mock(RemoteViews.class);
-        when(b.getLayoutId()).thenReturn(234);
-        when(b.getSequenceNumber()).thenReturn(1);
-
-        Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
-        Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
-        n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
-        n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
-        assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferent_null() {
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentSame() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentText() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
-                .build();
-
-        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentSpannables() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon,
-                        new SpannableStringBuilder().append("test1",
-                                new StyleSpan(Typeface.BOLD),
-                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
-                        intent).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "test1", intent).build())
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentNumber() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
-                .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
-                .build();
-
-        assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsDifferentIntent() {
-        PendingIntent intent1 = mock(PendingIntent.class);
-        PendingIntent intent2 = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build())
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testActionsIgnoresRemoteInputs() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        Notification n1 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(new RemoteInput.Builder("a")
-                                .setChoices(new CharSequence[] {"i", "m"})
-                                .build())
-                        .build())
-                .build();
-        Notification n2 = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(new RemoteInput.Builder("a")
-                                .setChoices(new CharSequence[] {"t", "m"})
-                                .build())
-                        .build())
-                .build();
-
-        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
-    }
-
-    @Test
-    public void testFreeformRemoteInputActionPair_noRemoteInput() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-        Notification notification = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .build())
-                .build();
-        assertNull(notification.findRemoteInputActionPair(false));
-    }
-
-    @Test
-    public void testFreeformRemoteInputActionPair_hasRemoteInput() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        RemoteInput remoteInput = new RemoteInput.Builder("a").build();
-
-        Notification.Action actionWithRemoteInput =
-                new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(remoteInput)
-                        .addRemoteInput(remoteInput)
-                        .build();
-
-        Notification.Action actionWithoutRemoteInput =
-                new Notification.Action.Builder(icon, "TEXT 2", intent)
-                        .build();
-
-        Notification notification = new Notification.Builder(mContext, "test")
-                .addAction(actionWithoutRemoteInput)
-                .addAction(actionWithRemoteInput)
-                .build();
-
-        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
-                notification.findRemoteInputActionPair(false);
-
-        assertNotNull(remoteInputActionPair);
-        assertEquals(remoteInput, remoteInputActionPair.first);
-        assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
-    }
-
-    @Test
-    public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-        Notification notification = new Notification.Builder(mContext, "test")
-                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(
-                                new RemoteInput.Builder("a")
-                                        .setAllowFreeFormInput(false).build())
-                        .build())
-                .build();
-        assertNull(notification.findRemoteInputActionPair(true));
-    }
-
-    @Test
-    public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
-        PendingIntent intent = mock(PendingIntent.class);
-        Icon icon = mock(Icon.class);
-
-        RemoteInput remoteInput =
-                new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
-        RemoteInput freeformRemoteInput =
-                new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();
-
-        Notification.Action actionWithFreeformRemoteInput =
-                new Notification.Action.Builder(icon, "TEXT 1", intent)
-                        .addRemoteInput(remoteInput)
-                        .addRemoteInput(freeformRemoteInput)
-                        .build();
-
-        Notification.Action actionWithoutFreeformRemoteInput =
-                new Notification.Action.Builder(icon, "TEXT 2", intent)
-                        .addRemoteInput(remoteInput)
-                        .build();
-
-        Notification notification = new Notification.Builder(mContext, "test")
-                .addAction(actionWithoutFreeformRemoteInput)
-                .addAction(actionWithFreeformRemoteInput)
-                .build();
-
-        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
-                notification.findRemoteInputActionPair(true);
-
-        assertNotNull(remoteInputActionPair);
-        assertEquals(freeformRemoteInput, remoteInputActionPair.first);
-        assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
-    }
-}
-