Merge "Add flag to enable Haptics Customization feature" into main
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 560e41b..b8d251f 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -8,3 +8,10 @@
     description: "Whether the feature to sync different window-related config updates is enabled"
     bug: "260873529"
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "activity_embedding_overlay_presentation_flag"
+    description: "Whether the overlay presentation feature is enabled"
+    bug: "243518738"
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 03480e4..302c7fa 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5480,6 +5480,13 @@
          of known compatibility issues. -->
     <string-array name="config_highRefreshRateBlacklist"></string-array>
 
+    <!-- The list of packages to automatically opt in to refresh rate suppressing by small area
+    detection. Format of this array should be packageName:threshold and threshold value should
+     be between 0 to 1-->
+    <string-array name="config_smallAreaDetectionAllowlist" translatable="false">
+        <!-- Add packages:threshold here -->
+    </string-array>
+
     <!-- The list of packages to force slowJpegMode for Apps using Camera API1 -->
     <string-array name="config_forceSlowJpegModeList" translatable="false">
         <!-- Add packages here -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7ea5974..02209a7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4288,6 +4288,8 @@
   <java-symbol type="array" name="config_highRefreshRateBlacklist" />
   <java-symbol type="array" name="config_forceSlowJpegModeList" />
 
+  <java-symbol type="array" name="config_smallAreaDetectionAllowlist" />
+
   <java-symbol type="layout" name="chooser_dialog" />
   <java-symbol type="layout" name="chooser_dialog_item" />
   <java-symbol type="drawable" name="chooser_dialog_background" />
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index d437e35..696e877 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -18,6 +18,7 @@
 
 import androidx.activity.compose.BackHandler
 import androidx.appcompat.R
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.RowScope
@@ -96,7 +97,8 @@
             Modifier
                 .padding(paddingValues.horizontalValues())
                 .padding(top = paddingValues.calculateTopPadding())
-                .fillMaxSize(),
+                .focusable()
+                .fillMaxSize()
         ) {
             content(
                 paddingValues.calculateBottomPadding(),
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index f68078a..82b0324 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.Compile
 import com.android.systemui.util.traceSection
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -239,7 +240,7 @@
 
     private companion object {
         const val TAG = "DisplayRepository"
-        val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+        val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG
     }
 }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b994105..f3ad4b4 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -472,6 +472,8 @@
     private SensorManager mSensorManager;
     private BrightnessTracker mBrightnessTracker;
 
+    private SmallAreaDetectionController mSmallAreaDetectionController;
+
 
     // Whether minimal post processing is allowed by the user.
     @GuardedBy("mSyncRoot")
@@ -738,6 +740,8 @@
         filter.addAction(Intent.ACTION_DOCK_EVENT);
 
         mContext.registerReceiver(mIdleModeReceiver, filter);
+
+        mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext);
     }
 
     @VisibleForTesting
@@ -3128,6 +3132,9 @@
         pw.println();
         mDisplayModeDirector.dump(pw);
         mBrightnessSynchronizer.dump(pw);
+        if (mSmallAreaDetectionController != null) {
+            mSmallAreaDetectionController.dump(pw);
+        }
     }
 
     private static float[] getFloatArray(TypedArray array) {
diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
new file mode 100644
index 0000000..adaa539
--- /dev/null
+++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 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.display;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Map;
+
+final class SmallAreaDetectionController {
+    private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds);
+    private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold);
+
+    // TODO(b/281720315): Move this to DeviceConfig once server side ready.
+    private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST =
+            "small_area_detection_allowlist";
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final PackageManagerInternal mPackageManager;
+    private final UserManagerInternal mUserManager;
+    @GuardedBy("mLock")
+    private final Map<String, Float> mAllowPkgMap = new ArrayMap<>();
+    // TODO(b/298722189): Update allowlist when user changes
+    @GuardedBy("mLock")
+    private int[] mUserIds;
+
+    static SmallAreaDetectionController create(@NonNull Context context) {
+        final SmallAreaDetectionController controller =
+                new SmallAreaDetectionController(context, DeviceConfigInterface.REAL);
+        final String property = DeviceConfigInterface.REAL.getProperty(
+                DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_SMALL_AREA_DETECTION_ALLOWLIST);
+        controller.updateAllowlist(property);
+        return controller;
+    }
+
+    @VisibleForTesting
+    SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
+        mContext = context;
+        mPackageManager = LocalServices.getService(PackageManagerInternal.class);
+        mUserManager = LocalServices.getService(UserManagerInternal.class);
+        deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                BackgroundThread.getExecutor(),
+                new SmallAreaDetectionController.OnPropertiesChangedListener());
+        mPackageManager.getPackageList(new PackageReceiver());
+    }
+
+    @VisibleForTesting
+    void updateAllowlist(@Nullable String property) {
+        synchronized (mLock) {
+            mAllowPkgMap.clear();
+            if (property != null) {
+                final String[] mapStrings = property.split(",");
+                for (String mapString : mapStrings) putToAllowlist(mapString);
+            } else {
+                final String[] defaultMapStrings = mContext.getResources()
+                        .getStringArray(R.array.config_smallAreaDetectionAllowlist);
+                for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);
+            }
+            updateSmallAreaDetection();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void putToAllowlist(String rowData) {
+        // Data format: package:threshold - e.g. "com.abc.music:0.05"
+        final String[] items = rowData.split(":");
+        if (items.length == 2) {
+            try {
+                final String pkg = items[0];
+                final float threshold = Float.valueOf(items[1]);
+                mAllowPkgMap.put(pkg, threshold);
+            } catch (Exception e) {
+                // Just skip if items[1] - the threshold is not parsable number
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) {
+        for (int i = 0; i < mUserIds.length; i++) {
+            final int userId = mUserIds[i];
+            final int uid = mPackageManager.getPackageUid(pkg, 0, userId);
+            if (uid > 0) list.put(uid, threshold);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateSmallAreaDetection() {
+        if (mAllowPkgMap.isEmpty()) return;
+
+        mUserIds = mUserManager.getUserIds();
+
+        final SparseArray<Float> uidThresholdList = new SparseArray<>();
+        for (String pkg : mAllowPkgMap.keySet()) {
+            final float threshold = mAllowPkgMap.get(pkg);
+            updateUidListForAllUsers(uidThresholdList, pkg, threshold);
+        }
+
+        final int[] uids = new int[uidThresholdList.size()];
+        final float[] thresholds = new float[uidThresholdList.size()];
+        for (int i = 0; i < uidThresholdList.size();  i++) {
+            uids[i] = uidThresholdList.keyAt(i);
+            thresholds[i] = uidThresholdList.valueAt(i);
+        }
+        updateSmallAreaDetection(uids, thresholds);
+    }
+
+    @VisibleForTesting
+    void updateSmallAreaDetection(int[] uids, float[] thresholds) {
+        nativeUpdateSmallAreaDetection(uids, thresholds);
+    }
+
+    void setSmallAreaDetectionThreshold(int uid, float threshold) {
+        nativeSetSmallAreaDetectionThreshold(uid, threshold);
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("Small area detection allowlist");
+        pw.println("  Packages:");
+        synchronized (mLock) {
+            for (String pkg : mAllowPkgMap.keySet()) {
+                pw.println("    " + pkg + " threshold = " + mAllowPkgMap.get(pkg));
+            }
+            pw.println("  mUserIds=" + Arrays.toString(mUserIds));
+        }
+    }
+
+    private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener {
+        public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+            if (properties.getKeyset().contains(KEY_SMALL_AREA_DETECTION_ALLOWLIST)) {
+                updateAllowlist(
+                        properties.getString(KEY_SMALL_AREA_DETECTION_ALLOWLIST, null /*default*/));
+            }
+        }
+    }
+
+    private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
+        @Override
+        public void onPackageAdded(@NonNull String packageName, int uid) {
+            synchronized (mLock) {
+                if (mAllowPkgMap.containsKey(packageName)) {
+                    setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName));
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageArchiverService.java b/services/core/java/com/android/server/pm/PackageArchiverService.java
index 1b25d6d..29f49c8 100644
--- a/services/core/java/com/android/server/pm/PackageArchiverService.java
+++ b/services/core/java/com/android/server/pm/PackageArchiverService.java
@@ -61,8 +61,6 @@
  */
 public class PackageArchiverService extends IPackageArchiverService.Stub {
 
-    private static final String TAG = "PackageArchiver";
-
     /**
      * The maximum time granted for an app store to start a foreground service when unarchival
      * is requested.
@@ -99,29 +97,45 @@
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "archiveApp");
         verifyCaller(providedUid, binderUid);
-        PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId);
-        if (getResponsibleInstallerPackage(ps) == null) {
-            throw new ParcelableException(
-                    new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("No installer found to archive app %s.",
-                                    packageName)));
+        ArchiveState archiveState;
+        try {
+            archiveState = createArchiveState(packageName, userId);
+            // TODO(b/282952870) Should be reverted if uninstall fails/cancels
+            storeArchiveState(packageName, archiveState, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new ParcelableException(e);
         }
 
-        // TODO(b/291569242) Verify that this list is not empty and return failure with
-        //  intentsender
-        List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
-                ps.getPackageName(),
-                new UserHandle(userId));
-
-        // TODO(b/282952870) Bug: should happen after the uninstall completes successfully
-        storeArchiveState(ps, mainActivities, userId);
-
         // TODO(b/278553670) Add special strings for the delete dialog
         mPm.mInstallerService.uninstall(
                 new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
                 callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
     }
 
+    private ArchiveState createArchiveState(String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
+        PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
+                Binder.getCallingUid(), userId);
+        String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
+        if (responsibleInstallerPackage == null) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("No installer found to archive app %s.",
+                            packageName));
+        }
+
+        List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId);
+        List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>();
+        for (int i = 0; i < mainActivities.size(); i++) {
+            // TODO(b/278553670) Extract and store launcher icons
+            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+                    mainActivities.get(i).getLabel().toString(),
+                    Path.of("/TODO"), null);
+            archiveActivityInfos.add(activityInfo);
+        }
+
+        return new ArchiveState(archiveActivityInfos, responsibleInstallerPackage);
+    }
+
     @Override
     public void requestUnarchive(
             @NonNull String packageName,
@@ -138,8 +152,13 @@
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "unarchiveApp");
         verifyCaller(providedUid, binderUid);
-        PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId);
-        verifyArchived(ps, userId);
+        PackageStateInternal ps;
+        try {
+            ps = getPackageState(packageName, snapshot, binderUid, userId);
+            verifyArchived(ps, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new ParcelableException(e);
+        }
         String installerPackage = getResponsibleInstallerPackage(ps);
         if (installerPackage == null) {
             throw new ParcelableException(
@@ -151,14 +170,14 @@
         mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage));
     }
 
-    private void verifyArchived(PackageStateInternal ps, int userId) {
+    private void verifyArchived(PackageStateInternal ps, int userId)
+            throws PackageManager.NameNotFoundException {
         PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
         // TODO(b/288142708) Check for isInstalled false here too.
         if (userState.getArchiveState() == null) {
-            throw new ParcelableException(
-                    new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("Package %s is not currently archived.",
-                                    ps.getPackageName())));
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s is not currently archived.",
+                            ps.getPackageName()));
         }
     }
 
@@ -196,6 +215,21 @@
                 /* initialExtras= */ null);
     }
 
+    private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps,
+            int userId) throws PackageManager.NameNotFoundException {
+        List<LauncherActivityInfo> mainActivities =
+                Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList(
+                        ps.getPackageName(),
+                        new UserHandle(userId)));
+        if (mainActivities.isEmpty()) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("The app %s does not have a main activity.",
+                            ps.getPackageName()));
+        }
+
+        return mainActivities;
+    }
+
     @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
             android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
             android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
@@ -219,13 +253,13 @@
 
     @NonNull
     private static PackageStateInternal getPackageState(String packageName,
-            Computer snapshot, int callingUid, int userId) {
+            Computer snapshot, int callingUid, int userId)
+            throws PackageManager.NameNotFoundException {
         PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
                 userId);
         if (ps == null) {
-            throw new ParcelableException(
-                    new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("Package %s not found.", packageName)));
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s not found.", packageName));
         }
         return ps;
     }
@@ -237,38 +271,25 @@
         return mLauncherApps;
     }
 
-    private void storeArchiveState(PackageStateInternal ps,
-            List<LauncherActivityInfo> mainActivities, int userId) {
-        List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
-        for (int i = 0; i < mainActivities.size(); i++) {
-            // TODO(b/278553670) Extract and store launcher icons
-            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
-                    mainActivities.get(i).getLabel().toString(),
-                    Path.of("/TODO"), null);
-            activityInfos.add(activityInfo);
-        }
-
-        InstallSource installSource = ps.getInstallSource();
-        String installerPackageName = installSource.mUpdateOwnerPackageName != null
-                ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;
-
+    private void storeArchiveState(String packageName, ArchiveState archiveState, int userId)
+            throws PackageManager.NameNotFoundException {
         synchronized (mPm.mLock) {
-            PackageSetting packageSetting = getPackageSettingLocked(ps.getPackageName(), userId);
+            PackageSetting packageSetting = getPackageSettingLocked(packageName, userId);
             packageSetting
                     .modifyUserState(userId)
-                    .setArchiveState(new ArchiveState(activityInfos, installerPackageName));
+                    .setArchiveState(archiveState);
         }
     }
 
     @NonNull
     @GuardedBy("mPm.mLock")
-    private PackageSetting getPackageSettingLocked(String packageName, int userId) {
+    private PackageSetting getPackageSettingLocked(String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
         PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
         // Shouldn't happen, we already verify presence of the package in getPackageState()
         if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
-            throw new ParcelableException(
-                    new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("Package %s not found.", packageName)));
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s not found.", packageName));
         }
         return ps;
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index cbac39a..63794d5 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3354,6 +3354,13 @@
                     return true;
                 }
                 break;
+            case KeyEvent.KEYCODE_DEL:
+            case KeyEvent.KEYCODE_GRAVE:
+                if (firstDown && event.isMetaPressed()) {
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                    injectBackGesture(event.getDownTime());
+                    return true;
+                }
             case KeyEvent.KEYCODE_DPAD_UP:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
@@ -3365,9 +3372,14 @@
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_LEFT:
-                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
-                    enterStageSplitFromRunningApp(true /* leftOrTop */);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                if (firstDown && event.isMetaPressed()) {
+                    if (event.isCtrlPressed()) {
+                        enterStageSplitFromRunningApp(true /* leftOrTop */);
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                    } else {
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                        injectBackGesture(event.getDownTime());
+                    }
                     return true;
                 }
                 break;
@@ -3630,6 +3642,25 @@
         return (metaState & KeyEvent.META_META_ON) != 0;
     }
 
+    @SuppressLint("MissingPermission")
+    private void injectBackGesture(long downtime) {
+        // Create and inject down event
+        KeyEvent downEvent = new KeyEvent(downtime, downtime, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
+                KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                InputDevice.SOURCE_KEYBOARD);
+        mInputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+
+
+        // Create and inject up event
+        KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP);
+        mInputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+
+        downEvent.recycle();
+        upEvent.recycle();
+    }
+
     private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
         // First we always handle the home key here, so applications
         // can never break it, although if keyguard is on, we do let
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 53d1adf..fd42077 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6183,6 +6183,8 @@
         @Override
         public void onPackageReplaced(ApplicationInfo aInfo) {
             synchronized (mGlobalLock) {
+                // In case if setWindowManager hasn't been called yet when booting.
+                if (mRootWindowContainer == null) return;
                 mRootWindowContainer.updateActivityApplicationInfo(aInfo);
             }
         }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 2a995b2..ec5378f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -41,6 +41,7 @@
         "com_android_server_companion_virtual_InputController.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
         "com_android_server_display_DisplayControl.cpp",
+        "com_android_server_display_SmallAreaDetectionController.cpp",
         "com_android_server_connectivity_Vpn.cpp",
         "com_android_server_gpu_GpuService.cpp",
         "com_android_server_HardwarePropertiesManagerService.cpp",
diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
new file mode 100644
index 0000000..b256f16
--- /dev/null
+++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#define LOG_TAG "SmallAreaDetectionController"
+
+#include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+
+#include "jni.h"
+#include "utils/Log.h"
+
+namespace android {
+static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids,
+                                           jfloatArray jthresholds) {
+    if (juids == nullptr || jthresholds == nullptr) return;
+
+    ScopedIntArrayRO uids(env, juids);
+    ScopedFloatArrayRO thresholds(env, jthresholds);
+
+    if (uids.size() != thresholds.size()) {
+        ALOGE("uids size exceeds thresholds size!");
+        return;
+    }
+
+    std::vector<int32_t> uidVector;
+    std::vector<float> thresholdVector;
+    size_t size = uids.size();
+    uidVector.reserve(size);
+    thresholdVector.reserve(size);
+    for (int i = 0; i < size; i++) {
+        uidVector.push_back(static_cast<int32_t>(uids[i]));
+        thresholdVector.push_back(static_cast<float>(thresholds[i]));
+    }
+    SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector);
+}
+
+static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid,
+                                                 jfloat threshold) {
+    SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold);
+}
+
+static const JNINativeMethod gMethods[] = {
+        {"nativeUpdateSmallAreaDetection", "([I[F)V", (void*)nativeUpdateSmallAreaDetection},
+        {"nativeSetSmallAreaDetectionThreshold", "(IF)V",
+         (void*)nativeSetSmallAreaDetectionThreshold},
+};
+
+int register_android_server_display_smallAreaDetectionController(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/display/SmallAreaDetectionController",
+                                    gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 97d7be6..f6f6737 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -67,6 +67,7 @@
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
 int register_com_android_server_SystemClockTime(JNIEnv* env);
+int register_android_server_display_smallAreaDetectionController(JNIEnv* env);
 };
 
 using namespace android;
@@ -126,5 +127,6 @@
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
     register_com_android_server_SystemClockTime(env);
+    register_android_server_display_smallAreaDetectionController(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index fb14419..e28028f9 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -35,6 +35,7 @@
         "mockingservicestests-utils-mockito",
         "platform-compat-test-rules",
         "platform-test-annotations",
+        "service-permission.stubs.system_server",
         "services.core",
         "servicestests-utils",
         "testables",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 40d7a77..a23539e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -64,6 +64,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -113,6 +114,7 @@
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.lights.LightsManager;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -291,10 +293,11 @@
     @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
     @Mock IBinder mMockDisplayToken;
     @Mock SensorManagerInternal mMockSensorManagerInternal;
-
     @Mock SensorManager mSensorManager;
-
     @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
+    @Mock PackageManagerInternal mMockPackageManagerInternal;
+    @Mock UserManagerInternal mMockUserManagerInternal;
+
 
     @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
     @Mock DisplayManagerFlags mMockFlags;
@@ -315,6 +318,10 @@
         LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
         LocalServices.addService(
                 VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
         // TODO: b/287945043
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mResources = Mockito.spy(mContext.getResources());
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
new file mode 100644
index 0000000..1ce79a5
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 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.display;
+
+import static android.os.Process.INVALID_UID;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.provider.DeviceConfigInterface;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SmallAreaDetectionControllerTest {
+
+    @Rule
+    public MockitoRule mRule = MockitoJUnit.rule();
+
+    @Mock
+    private PackageManagerInternal mMockPackageManagerInternal;
+    @Mock
+    private UserManagerInternal mMockUserManagerInternal;
+
+    private SmallAreaDetectionController mSmallAreaDetectionController;
+
+    private static final String PKG_A = "com.a.b.c";
+    private static final String PKG_B = "com.d.e.f";
+    private static final String PKG_NOT_INSTALLED = "com.not.installed";
+    private static final float THRESHOLD_A = 0.05f;
+    private static final float THRESHOLD_B = 0.07f;
+    private static final int USER_1 = 110;
+    private static final int USER_2 = 111;
+    private static final int UID_A_1 = 11011111;
+    private static final int UID_A_2 = 11111111;
+    private static final int UID_B_1 = 11022222;
+    private static final int UID_B_2 = 11122222;
+
+    @Before
+    public void setup() {
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
+
+        when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2});
+        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn(
+                INVALID_UID);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn(
+                INVALID_UID);
+
+        mSmallAreaDetectionController = spy(new SmallAreaDetectionController(
+                new ContextWrapper(ApplicationProvider.getApplicationContext()),
+                DeviceConfigInterface.REAL));
+        doNothing().when(mSmallAreaDetectionController).updateSmallAreaDetection(any(), any());
+    }
+
+    @Test
+    public void testUpdateAllowlist_validProperty() {
+        final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2};
+        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+                eq(resultThresholdArray));
+    }
+
+    @Test
+    public void testUpdateAllowlist_includeInvalidRow() {
+        final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        final int[] resultUidArray = {UID_B_1, UID_B_2};
+        final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+                eq(resultThresholdArray));
+    }
+
+    @Test
+    public void testUpdateAllowlist_includeNotInstalledPkg() {
+        final String property =
+                PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        final int[] resultUidArray = {UID_A_1, UID_A_2};
+        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+                eq(resultThresholdArray));
+    }
+
+    @Test
+    public void testUpdateAllowlist_invalidProperty() {
+        final String property = PKG_A;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        verify(mSmallAreaDetectionController, never()).updateSmallAreaDetection(any(), any());
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
index 473cece..e58a234 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
@@ -47,6 +47,7 @@
 import android.os.ParcelableException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -76,7 +77,6 @@
     private static final String CALLER_PACKAGE = "com.caller";
     private static final String INSTALLER_PACKAGE = "com.installer";
 
-
     @Rule
     public final MockSystemRule mMockSystem = new MockSystemRule();
 
@@ -212,6 +212,20 @@
     }
 
     @Test
+    public void archiveApp_noMainActivities() {
+        when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
+                List.of());
+
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
+    }
+
+    @Test
     public void archiveApp_success() {
         mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
 
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 8fadecd..e13dc3e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -65,6 +65,12 @@
                         KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
                 {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
                         KeyEvent.KEYCODE_BACK, 0},
+                {"Meta + `(grave) -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_GRAVE},
+                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_GRAVE, META_ON},
+                {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON},
+                {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
+                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DEL, META_ON},
                 {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
                         KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0},
                 {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},