Merge "Add MainSwitchPreference support for Catalyst" into main
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e7f4dbc..ed6b3d4a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -237,6 +237,7 @@
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SafeZipPathValidatorCallback;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.internal.policy.DecorView;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
@@ -7675,6 +7676,16 @@
                 }
             }
         });
+
+        // Register callback to report native memory metrics post GC cleanup
+        if (Flags.reportPostgcMemoryMetrics() &&
+            com.android.libcore.readonly.Flags.postCleanupApis()) {
+            VMRuntime.addPostCleanupCallback(new Runnable() {
+                @Override public void run() {
+                    MetricsLoggerWrapper.logPostGcMemorySnapshot();
+                }
+            });
+        }
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index e882bb5..081ce31 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -345,6 +345,15 @@
      */
     public AppCompatTaskInfo appCompatTaskInfo = AppCompatTaskInfo.create();
 
+    /**
+     * The top activity's main window frame if it doesn't match the top activity bounds.
+     * {@code null}, otherwise.
+     *
+     * @hide
+     */
+    @Nullable
+    public Rect topActivityMainWindowFrame;
+
     TaskInfo() {
         // Do nothing
     }
@@ -477,7 +486,8 @@
                 && Objects.equals(capturedLink, that.capturedLink)
                 && capturedLinkTimestamp == that.capturedLinkTimestamp
                 && requestedVisibleTypes == that.requestedVisibleTypes
-                && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo);
+                && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo)
+                && Objects.equals(topActivityMainWindowFrame, that.topActivityMainWindowFrame);
     }
 
     /**
@@ -553,6 +563,7 @@
         capturedLinkTimestamp = source.readLong();
         requestedVisibleTypes = source.readInt();
         appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
+        topActivityMainWindowFrame = source.readTypedObject(Rect.CREATOR);
     }
 
     /**
@@ -606,6 +617,7 @@
         dest.writeLong(capturedLinkTimestamp);
         dest.writeInt(requestedVisibleTypes);
         dest.writeTypedObject(appCompatTaskInfo, flags);
+        dest.writeTypedObject(topActivityMainWindowFrame, flags);
     }
 
     @Override
@@ -649,6 +661,7 @@
                 + " capturedLinkTimestamp=" + capturedLinkTimestamp
                 + " requestedVisibleTypes=" + requestedVisibleTypes
                 + " appCompatTaskInfo=" + appCompatTaskInfo
+                + " topActivityMainWindowFrame=" + topActivityMainWindowFrame
                 + "}";
     }
 }
diff --git a/core/java/android/app/metrics.aconfig b/core/java/android/app/metrics.aconfig
new file mode 100644
index 0000000..488f1c7
--- /dev/null
+++ b/core/java/android/app/metrics.aconfig
@@ -0,0 +1,10 @@
+package: "android.app"
+container: "system"
+
+flag {
+     namespace: "system_performance"
+     name: "report_postgc_memory_metrics"
+     is_exported: false
+     description: "Controls whether to report memory metrics post GC cleanup"
+     bug: "331243037"
+}
diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/core/java/com/android/internal/os/ProcfsMemoryUtil.java
similarity index 60%
rename from services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
rename to core/java/com/android/internal/os/ProcfsMemoryUtil.java
index 6cb6dc0..382f6c4 100644
--- a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
+++ b/core/java/com/android/internal/os/ProcfsMemoryUtil.java
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.stats.pull;
+package com.android.internal.os;
 
-import static android.os.Process.PROC_OUT_STRING;
+import static android.os.Process.*;
 
 import android.annotation.Nullable;
 import android.os.Process;
@@ -23,6 +23,7 @@
 
 public final class ProcfsMemoryUtil {
     private static final int[] CMDLINE_OUT = new int[] { PROC_OUT_STRING };
+    private static final int[] OOM_SCORE_ADJ_OUT = new int[] { PROC_NEWLINE_TERM | PROC_OUT_LONG };
     private static final String[] STATUS_KEYS = new String[] {
             "Uid:",
             "VmHWM:",
@@ -38,17 +39,34 @@
     private ProcfsMemoryUtil() {}
 
     /**
-     * Reads memory stats of a process from procfs. Returns values of the VmHWM, VmRss, AnonRSS,
-     * VmSwap, RssShmem fields in /proc/pid/status in kilobytes or null if not available.
+     * Reads memory stats of a process from procfs.
+     *
+     * Returns values of the VmHWM, VmRss, AnonRSS, VmSwap, RssShmem fields in
+     * /proc/pid/status in kilobytes or null if not available.
      */
     @Nullable
     public static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
+        return readMemorySnapshotFromProcfs("/proc/" + pid + "/status");
+    }
+
+    /**
+     * Reads memory stats of the current process from procfs.
+     *
+     * Returns values of the VmHWM, VmRss, AnonRSS, VmSwap, RssShmem fields in
+     * /proc/self/status in kilobytes or null if not available.
+     */
+    @Nullable
+    public static MemorySnapshot readMemorySnapshotFromProcfs() {
+        return readMemorySnapshotFromProcfs("/proc/self/status");
+    }
+
+    private static MemorySnapshot readMemorySnapshotFromProcfs(String path) {
         long[] output = new long[STATUS_KEYS.length];
         output[0] = -1;
         output[3] = -1;
         output[4] = -1;
         output[5] = -1;
-        Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output);
+        Process.readProcLines(path, STATUS_KEYS, output);
         if (output[0] == -1 || output[3] == -1 || output[4] == -1 || output[5] == -1) {
             // Could not open or parse file.
             return null;
@@ -70,14 +88,54 @@
      * if the file is not available.
      */
     public static String readCmdlineFromProcfs(int pid) {
+        return readCmdlineFromProcfs("/proc/" + pid + "/cmdline");
+    }
+
+    /**
+     * Reads cmdline of the current process from procfs.
+     *
+     * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
+     * if the file is not available.
+     */
+    public static String readCmdlineFromProcfs() {
+        return readCmdlineFromProcfs("/proc/self/cmdline");
+    }
+
+    private static String readCmdlineFromProcfs(String path) {
         String[] cmdline = new String[1];
-        if (!Process.readProcFile("/proc/" + pid + "/cmdline", CMDLINE_OUT, cmdline, null, null)) {
+        if (!Process.readProcFile(path, CMDLINE_OUT, cmdline, null, null)) {
             return "";
         }
         return cmdline[0];
     }
 
     /**
+     * Reads oom_score_adj of a process from procfs
+     *
+     * Returns content of /proc/pid/oom_score_adj. Defaults to 0 if reading fails.
+     */
+    public static int readOomScoreAdjFromProcfs(int pid) {
+        return readOomScoreAdjFromProcfs("/proc/" + pid + "/oom_score_adj");
+    }
+
+    /**
+     * Reads oom_score_adj of the current process from procfs
+     *
+     * Returns content of /proc/pid/oom_score_adj. Defaults to 0 if reading fails.
+     */
+    public static int readOomScoreAdjFromProcfs() {
+        return readOomScoreAdjFromProcfs("/proc/self/oom_score_adj");
+    }
+
+    private static int readOomScoreAdjFromProcfs(String path) {
+        long[] oom_score_adj = new long[1];
+        if (Process.readProcFile(path, OOM_SCORE_ADJ_OUT, null, oom_score_adj, null)) {
+            return (int)oom_score_adj[0];
+        }
+        return 0;
+    }
+
+    /**
      * Scans all /proc/pid/cmdline entries and returns a mapping between pid and cmdline.
      */
     public static SparseArray<String> getProcessCmdlines() {
@@ -109,7 +167,7 @@
 
     /** Reads and parses selected entries of /proc/vmstat. */
     @Nullable
-    static VmStat readVmStat() {
+    public static VmStat readVmStat() {
         long[] vmstat = new long[VMSTAT_KEYS.length];
         vmstat[0] = -1;
         Process.readProcLines("/proc/vmstat", VMSTAT_KEYS, vmstat);
@@ -121,7 +179,7 @@
         return result;
     }
 
-    static final class VmStat {
+    public static final class VmStat {
         public int oomKillCount;
     }
 }
diff --git a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
index b42ea7d..e2237f6 100644
--- a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
+++ b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
@@ -16,9 +16,15 @@
 
 package com.android.internal.os.logging;
 
+import android.app.Application;
+import android.os.Process;
+import android.util.Log;
 import android.view.WindowManager.LayoutParams;
 
+import com.android.internal.os.ProcfsMemoryUtil;
 import com.android.internal.util.FrameworkStatsLog;
+import java.util.Collection;
+import libcore.util.NativeAllocationRegistry;
 
 /**
  * Used to wrap different logging calls in one, so that client side code base is clean and more
@@ -49,4 +55,46 @@
             }
         }
     }
+
+    public static void logPostGcMemorySnapshot() {
+        if (!com.android.libcore.Flags.nativeMetrics()) {
+            return;
+        }
+        int pid = Process.myPid();
+        String processName = Application.getProcessName();
+        Collection<NativeAllocationRegistry.Metrics> metrics =
+            NativeAllocationRegistry.getMetrics();
+        int nMetrics = metrics.size();
+
+        String[] classNames = new String[nMetrics];
+        long[] mallocedCount = new long[nMetrics];
+        long[] mallocedBytes = new long[nMetrics];
+        long[] nonmallocedCount = new long[nMetrics];
+        long[] nonmallocedBytes = new long[nMetrics];
+
+        int i = 0;
+        for (NativeAllocationRegistry.Metrics m : metrics) {
+            classNames[i] = m.getClassName();
+            mallocedCount[i] = m.getMallocedCount();
+            mallocedBytes[i] = m.getMallocedBytes();
+            nonmallocedCount[i] = m.getNonmallocedCount();
+            nonmallocedBytes[i] = m.getNonmallocedBytes();
+            i++;
+        }
+
+        ProcfsMemoryUtil.MemorySnapshot m = ProcfsMemoryUtil.readMemorySnapshotFromProcfs();
+        int oom_score_adj = ProcfsMemoryUtil.readOomScoreAdjFromProcfs();
+        FrameworkStatsLog.write(FrameworkStatsLog.POSTGC_MEMORY_SNAPSHOT,
+            m.uid, processName, pid,
+            oom_score_adj,
+            m.rssInKilobytes,
+            m.anonRssInKilobytes,
+            m.swapInKilobytes,
+            m.anonRssInKilobytes + m.swapInKilobytes,
+            classNames,
+            mallocedCount,
+            mallocedBytes,
+            nonmallocedCount,
+            nonmallocedBytes);
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index 6ad2f08..220fc6f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -54,6 +54,7 @@
     @NonNull
     private final BackupIdler mBackupIdler = new BackupIdler();
     private boolean mBackupIdlerScheduled;
+    private boolean mSaveEmbeddingState = false;
 
     private final List<ParcelableTaskContainerData> mParcelableTaskContainerDataList =
             new ArrayList<>();
@@ -71,11 +72,32 @@
         }
     }
 
+    void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
+        if (mSaveEmbeddingState == saveEmbeddingState) {
+            return;
+        }
+
+        Log.i(TAG, "Set save embedding state: " + saveEmbeddingState);
+        mSaveEmbeddingState = saveEmbeddingState;
+        if (!mSaveEmbeddingState) {
+            removeSavedState();
+            return;
+        }
+
+        if (!hasPendingStateToRestore() && !mController.getTaskContainers().isEmpty()) {
+            scheduleBackup();
+        }
+    }
     /**
      * Schedules a back-up request. It is no-op if there was a request scheduled and not yet
      * completed.
      */
     void scheduleBackup() {
+        if (!mSaveEmbeddingState) {
+            // TODO(b/289875940): enabled internally for broader testing.
+            return;
+        }
+
         if (!mBackupIdlerScheduled) {
             mBackupIdlerScheduled = true;
             Looper.getMainLooper().getQueue().addIdleHandler(mBackupIdler);
@@ -128,7 +150,6 @@
         final List<TaskFragmentInfo> infos = savedState.getParcelableArrayList(
                 KEY_RESTORE_TASK_FRAGMENTS_INFO, TaskFragmentInfo.class);
         for (TaskFragmentInfo info : infos) {
-            if (DEBUG) Log.d(TAG, "Retrieved: " + info);
             mTaskFragmentInfos.put(info.getFragmentToken(), info);
             mPresenter.updateTaskFragmentInfo(info);
         }
@@ -140,6 +161,11 @@
             if (DEBUG) Log.d(TAG, "Retrieved: " + info);
             mTaskFragmentParentInfos.put(info.getTaskId(), info);
         }
+
+        if (DEBUG) {
+            Log.d(TAG, "Retrieved task-fragment info: " + infos.size() + ", task info: "
+                    + parentInfos.size());
+        }
     }
 
     void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
@@ -148,7 +174,6 @@
             final TaskFragmentInfo info = mTaskFragmentInfos.valueAt(i);
             mPresenter.deleteTaskFragment(wct, info.getFragmentToken());
         }
-
         removeSavedState();
     }
 
@@ -190,6 +215,9 @@
         final ArrayMap<String, EmbeddingRule> embeddingRuleMap = new ArrayMap<>();
         for (EmbeddingRule rule : rules) {
             embeddingRuleMap.put(rule.getTag(), rule);
+            if (DEBUG) {
+                Log.d(TAG, "Tag: " + rule.getTag() + " rule: " + rule);
+            }
         }
 
         boolean restoredAny = false;
@@ -201,7 +229,7 @@
                 // has unknown tag, unable to restore.
                 if (DEBUG) {
                     Log.d(TAG, "Rebuilding TaskContainer abort! Unknown Tag. Task#"
-                            + parcelableTaskContainerData.mTaskId);
+                            + parcelableTaskContainerData.mTaskId + ", tags = " + tags);
                 }
                 continue;
             }
@@ -217,7 +245,7 @@
 
             final TaskContainer taskContainer = new TaskContainer(parcelableTaskContainerData,
                     mController, mTaskFragmentInfos);
-            if (DEBUG) Log.d(TAG, "Created TaskContainer " + taskContainer);
+            if (DEBUG) Log.d(TAG, "Created TaskContainer " + taskContainer.getTaskId());
             mController.addTaskContainer(taskContainer.getTaskId(), taskContainer);
 
             for (ParcelableSplitContainerData splitData :
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 3368e2e..60e1a50 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2886,6 +2886,18 @@
         return getActiveSplitForContainer(container) != null;
     }
 
+
+    @Override
+    public void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
+        if (!Flags.aeBackStackRestore()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            mPresenter.setAutoSaveEmbeddingState(saveEmbeddingState);
+        }
+    }
+
     void scheduleBackup() {
         synchronized (mLock) {
             mPresenter.scheduleBackup();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index b498ee2..9a2f32e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -183,6 +183,10 @@
         }
     }
 
+    void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
+        mBackupHelper.setAutoSaveEmbeddingState(saveEmbeddingState);
+    }
+
     void scheduleBackup() {
         mBackupHelper.scheduleBackup();
     }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index b453f1d..6928409 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -48,8 +48,6 @@
 import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
 import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
 
-import com.android.window.flags.Flags;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -634,11 +632,7 @@
         // pin container.
         updateAlwaysOnTopOverlayIfNecessary();
 
-        // TODO(b/289875940): Making backup-restore as an opt-in solution, before the flag goes
-        //  to next-food.
-        if (Flags.aeBackStackRestore()) {
-            mSplitController.scheduleBackup();
-        }
+        mSplitController.scheduleBackup();
     }
 
     private void updateAlwaysOnTopOverlayIfNecessary() {
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 65b2275..00ae05c 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -76,6 +76,7 @@
         "truth",
         "Nene",
         "Harrier",
+        "bedstead-enterprise",
     ],
     libs: [
         "android.test.base.stubs.system",
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index e86e727..9cce431 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -20,6 +20,9 @@
 import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS;
 import static android.provider.Settings.System.RINGTONE;
 
+import static com.android.bedstead.enterprise.EnterpriseDeviceStateExtensionsKt.workProfile;
+import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.secondaryUser;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.pm.PackageManager;
@@ -82,7 +85,7 @@
     @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
     @EnsureHasWorkProfile
     public void testSettings_workProfile() throws Exception {
-        UserReference profile = sDeviceState.workProfile();
+        UserReference profile = workProfile(sDeviceState);
 
         // Settings.Global settings are shared between different users
         assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), profile.id());
@@ -96,7 +99,7 @@
     @RequireRunOnInitialUser
     @EnsureHasSecondaryUser
     public void testSettings_secondaryUser() throws Exception {
-        UserReference secondaryUser = sDeviceState.secondaryUser();
+        UserReference secondaryUser = secondaryUser(sDeviceState);
 
         // Settings.Global settings are shared between different users
         assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), secondaryUser.id());
@@ -223,7 +226,7 @@
     @RequireRunOnInitialUser
     @EnsureHasSecondaryUser
     public void testSettings_stopAndRestartSecondaryUser() throws Exception {
-        UserReference secondaryUser = sDeviceState.secondaryUser();
+        UserReference secondaryUser = secondaryUser(sDeviceState);
 
         assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
 
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index ba4b71c..17fcbf4 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -22,7 +22,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.am.ProcessRecord.TAG;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import static com.android.internal.os.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -58,7 +58,7 @@
 import com.android.modules.expresslog.Counter;
 import com.android.server.ResourcePressureUtil;
 import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.internal.os.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.server.wm.WindowProcessController;
 
 import java.io.File;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e7735d8..54e4f8e 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -48,6 +48,9 @@
 import static android.util.MathUtils.constrain;
 import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
 
+import static com.android.internal.os.ProcfsMemoryUtil.getProcessCmdlines;
+import static com.android.internal.os.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.internal.os.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY;
@@ -68,9 +71,6 @@
 import static com.android.server.stats.Flags.applyNetworkStatsPollRateLimit;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
 import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
 
@@ -209,6 +209,8 @@
 import com.android.internal.os.LooperStats;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.ProcfsMemoryUtil;
+import com.android.internal.os.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.internal.os.SelectedProcessCpuThreadReader;
 import com.android.internal.os.StoragedUidIoStatsReader;
 import com.android.internal.util.CollectionUtils;
@@ -229,7 +231,6 @@
 import com.android.server.power.stats.KernelWakelockStats;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
-import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.server.stats.pull.netstats.NetworkStatsAccumulator;
 import com.android.server.stats.pull.netstats.NetworkStatsExt;
 import com.android.server.stats.pull.netstats.SubInfo;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 4f71719..567eca2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2572,14 +2572,21 @@
         wpc.computeProcessActivityState();
     }
 
-    void computeProcessActivityStateBatch() {
+    boolean computeProcessActivityStateBatch() {
         if (mActivityStateChangedProcs.isEmpty()) {
-            return;
+            return false;
         }
+        boolean changed = false;
         for (int i = mActivityStateChangedProcs.size() - 1; i >= 0; i--) {
-            mActivityStateChangedProcs.get(i).computeProcessActivityState();
+            final WindowProcessController wpc = mActivityStateChangedProcs.get(i);
+            final int prevState = wpc.getActivityStateFlags();
+            wpc.computeProcessActivityState();
+            if (!changed && prevState != wpc.getActivityStateFlags()) {
+                changed = true;
+            }
         }
         mActivityStateChangedProcs.clear();
+        return changed;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 49fcb18..6cc4b1e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -994,7 +994,9 @@
         // Ensure the final animation targets which hidden by transition could be visible.
         for (int i = 0; i < targets.size(); i++) {
             final WindowContainer wc = targets.get(i).mContainer;
-            wc.prepareSurfaces();
+            if (wc.mSurfaceControl != null) {
+                wc.prepareSurfaces();
+            }
         }
 
         // The pending builder could be cleared due to prepareSurfaces
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 41a5804..6707a27 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -105,6 +105,7 @@
 import android.content.pm.UserProperties;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.power.Mode;
@@ -153,6 +154,7 @@
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.utils.Slogf;
+import com.android.server.wm.utils.RegionUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -270,6 +272,8 @@
     private boolean mTaskLayersChanged = true;
     private int mTmpTaskLayerRank;
     private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
+    private Region mTmpOccludingRegion;
+    private Region mTmpTaskRegion;
 
     private String mDestroyAllActivitiesReason;
     private final Runnable mDestroyAllActivitiesRunnable = new Runnable() {
@@ -2921,6 +2925,11 @@
         });
     }
 
+    void invalidateTaskLayersAndUpdateOomAdjIfNeeded() {
+        mRankTaskLayersRunnable.mCheckUpdateOomAdj = true;
+        invalidateTaskLayers();
+    }
+
     void invalidateTaskLayers() {
         if (!mTaskLayersChanged) {
             mTaskLayersChanged = true;
@@ -2938,13 +2947,18 @@
         // Only rank for leaf tasks because the score of activity is based on immediate parent.
         forAllLeafTasks(task -> {
             final int oldRank = task.mLayerRank;
-            final ActivityRecord r = task.topRunningActivityLocked();
-            if (r != null && r.isVisibleRequested()) {
+            final int oldRatio = task.mNonOccludedFreeformAreaRatio;
+            task.mNonOccludedFreeformAreaRatio = 0;
+            if (task.isVisibleRequested()) {
                 task.mLayerRank = ++mTmpTaskLayerRank;
+                if (task.inFreeformWindowingMode()) {
+                    computeNonOccludedFreeformAreaRatio(task);
+                }
             } else {
                 task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
             }
-            if (task.mLayerRank != oldRank) {
+            if (task.mLayerRank != oldRank
+                    || task.mNonOccludedFreeformAreaRatio != oldRatio) {
                 task.forAllActivities(activity -> {
                     if (activity.hasProcess()) {
                         mTaskSupervisor.onProcessActivityStateChanged(activity.app,
@@ -2953,10 +2967,40 @@
                 });
             }
         }, true /* traverseTopToBottom */);
-
-        if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
-            mTaskSupervisor.computeProcessActivityStateBatch();
+        if (mTmpOccludingRegion != null) {
+            mTmpOccludingRegion.setEmpty();
         }
+        boolean changed = false;
+        if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
+            changed = mTaskSupervisor.computeProcessActivityStateBatch();
+        }
+        if (mRankTaskLayersRunnable.mCheckUpdateOomAdj) {
+            mRankTaskLayersRunnable.mCheckUpdateOomAdj = false;
+            if (changed) {
+                mService.updateOomAdj();
+            }
+        }
+    }
+
+    /** This method is called for visible freeform task from top to bottom. */
+    private void computeNonOccludedFreeformAreaRatio(@NonNull Task task) {
+        if (!com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()) {
+            return;
+        }
+        if (mTmpOccludingRegion == null) {
+            mTmpOccludingRegion = new Region();
+            mTmpTaskRegion = new Region();
+        }
+        final Rect taskBounds = task.getBounds();
+        mTmpTaskRegion.set(taskBounds);
+        // Exclude the area outside the display.
+        mTmpTaskRegion.op(task.mDisplayContent.getBounds(), Region.Op.INTERSECT);
+        // Exclude the area covered by the above tasks.
+        mTmpTaskRegion.op(mTmpOccludingRegion, Region.Op.DIFFERENCE);
+        task.mNonOccludedFreeformAreaRatio = 100 * RegionUtils.getAreaSize(mTmpTaskRegion)
+                        / (taskBounds.width() * taskBounds.height());
+        // Accumulate the occluding region for other visible tasks behind.
+        mTmpOccludingRegion.op(taskBounds, Region.Op.UNION);
     }
 
     void clearOtherAppTimeTrackers(AppTimeTracker except) {
@@ -3862,6 +3906,8 @@
     }
 
     private class RankTaskLayersRunnable implements Runnable {
+        boolean mCheckUpdateOomAdj;
+
         @Override
         public void run() {
             synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4861341..8a624b3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -432,6 +432,9 @@
     // This number will be assigned when we evaluate OOM scores for all visible tasks.
     int mLayerRank = LAYER_RANK_INVISIBLE;
 
+    /** A 0~100 ratio to indicate the percentage of visible area on screen of a freeform task. */
+    int mNonOccludedFreeformAreaRatio;
+
     /* Unique identifier for this task. */
     final int mTaskId;
     /* User for which this task was created. */
@@ -1207,6 +1210,28 @@
     }
 
     @Override
+    void onResize() {
+        super.onResize();
+        updateTaskLayerForFreeform();
+    }
+
+    @Override
+    void onMovedByResize() {
+        super.onMovedByResize();
+        updateTaskLayerForFreeform();
+    }
+
+    private void updateTaskLayerForFreeform() {
+        if (!com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()) {
+            return;
+        }
+        if (!isVisibleRequested() || !inFreeformWindowingMode()) {
+            return;
+        }
+        mRootWindowContainer.invalidateTaskLayersAndUpdateOomAdjIfNeeded();
+    }
+
+    @Override
     @Nullable
     ActivityRecord getTopResumedActivity() {
         if (!isLeafTask()) {
@@ -3410,6 +3435,36 @@
         info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
                 ? windowState.getRequestedVisibleTypes() : WindowInsets.Type.defaultVisible();
         AppCompatUtils.fillAppCompatTaskInfo(this, info, top);
+        info.topActivityMainWindowFrame = calculateTopActivityMainWindowFrameForTaskInfo(top);
+    }
+
+    /**
+     * Returns the top activity's main window frame if it doesn't match
+     * {@link ActivityRecord#getBounds() the top activity bounds}, or {@code null}, otherwise.
+     *
+     * @param top The top running activity of the task
+     */
+    @Nullable
+    private static Rect calculateTopActivityMainWindowFrameForTaskInfo(
+            @Nullable ActivityRecord top) {
+        if (!Flags.betterSupportNonMatchParentActivity()) {
+            return null;
+        }
+        if (top == null) {
+            return null;
+        }
+        final WindowState mainWindow = top.findMainWindow();
+        if (mainWindow == null) {
+            return null;
+        }
+        if (!mainWindow.mHaveFrame) {
+            return null;
+        }
+        final Rect windowFrame = mainWindow.getFrame();
+        if (top.getBounds().equals(windowFrame)) {
+            return null;
+        }
+        return windowFrame;
     }
 
     /**
@@ -5919,6 +5974,10 @@
             pw.print(prefix); pw.print("  mLastNonFullscreenBounds=");
             pw.println(mLastNonFullscreenBounds);
         }
+        if (mNonOccludedFreeformAreaRatio != 0) {
+            pw.print(prefix); pw.print("  mNonOccludedFreeformAreaRatio=");
+            pw.println(mNonOccludedFreeformAreaRatio);
+        }
         if (isLeafTask()) {
             pw.println(prefix + "  isSleeping=" + shouldSleepActivities());
             printThisActivity(pw, getTopPausingActivity(), dumpPackage, false,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3ffeacf..d6ba312 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -3027,7 +3027,11 @@
         // The task order may be changed by finishIfPossible() for adjusting focus if there are
         // nested tasks, so add all activities into a list to avoid missed removals.
         final ArrayList<ActivityRecord> removingActivities = new ArrayList<>();
-        forAllActivities((Consumer<ActivityRecord>) removingActivities::add);
+        forAllActivities((r) -> {
+            if (!r.finishing) {
+                removingActivities.add(r);
+            }
+        });
         for (int i = removingActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord r = removingActivities.get(i);
             if (withTransition && r.isVisible()) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 1a107c2..7f0c336 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -121,6 +121,11 @@
      */
     private static final int MAX_NUM_PERCEPTIBLE_FREEFORM =
             SystemProperties.getInt("persist.wm.max_num_perceptible_freeform", 1);
+    /**
+     * If the visible area percentage of a resumed freeform task is greater than or equal to this
+     * ratio, its process will have a higher priority.
+     */
+    private static final int PERCEPTIBLE_FREEFORM_VISIBLE_RATIO = 90;
 
     private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 200;
     private static final long RAPID_ACTIVITY_LAUNCH_MS = 500;
@@ -1234,6 +1239,7 @@
         boolean hasResumedFreeform = false;
         int minTaskLayer = Integer.MAX_VALUE;
         int stateFlags = 0;
+        int nonOccludedRatio = 0;
         final boolean wasResumed = hasResumedActivity();
         final boolean wasAnyVisible = (mActivityStateFlags
                 & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
@@ -1261,6 +1267,8 @@
                         stateFlags |= ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN;
                     } else if (windowingMode == WINDOWING_MODE_FREEFORM) {
                         hasResumedFreeform = true;
+                        nonOccludedRatio =
+                                Math.max(task.mNonOccludedFreeformAreaRatio, nonOccludedRatio);
                     }
                 }
                 if (minTaskLayer > 0) {
@@ -1298,7 +1306,8 @@
                 && com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
                 // Exclude task layer 1 because it is already the top most.
                 && minTaskLayer > 1) {
-            if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+            if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM
+                    || nonOccludedRatio >= PERCEPTIBLE_FREEFORM_VISIBLE_RATIO) {
                 stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
             } else {
                 stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE;
diff --git a/services/core/java/com/android/server/wm/utils/RegionUtils.java b/services/core/java/com/android/server/wm/utils/RegionUtils.java
index ce7776f..ff23145 100644
--- a/services/core/java/com/android/server/wm/utils/RegionUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RegionUtils.java
@@ -81,4 +81,15 @@
         Collections.reverse(rects);
         rects.forEach(rectConsumer);
     }
+
+    /** Returns the area size of the region. */
+    public static int getAreaSize(Region region) {
+        final RegionIterator regionIterator = new RegionIterator(region);
+        int area = 0;
+        final Rect rect = new Rect();
+        while (regionIterator.next(rect)) {
+            area += rect.width() * rect.height();
+        }
+        return area;
+    }
 }
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index f15e533..2f00a1b 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -32,6 +32,7 @@
         "androidx.test.runner",
         "truth",
         "Harrier",
+        "bedstead-multiuser",
     ],
     platform_apis: true,
     certificate: "platform",
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index 70a2d48..48cebd7 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.MOVE_PACKAGE;
 import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
 
+import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.secondaryUser;
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -112,9 +113,9 @@
         final UserReference primaryUser = sDeviceState.primaryUser();
         if (primaryUser.id() == UserHandle.myUserId()) {
             mCurrentUser = primaryUser;
-            mOtherUser = sDeviceState.secondaryUser();
+            mOtherUser = secondaryUser(sDeviceState);
         } else {
-            mCurrentUser = sDeviceState.secondaryUser();
+            mCurrentUser = secondaryUser(sDeviceState);
             mOtherUser = primaryUser;
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index cf1dcd0..7e8bd38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -285,6 +285,52 @@
     }
 
     @Test
+    public void testTaskLayerRankFreeform() {
+        mSetFlagsRule.enableFlags(com.android.window.flags.Flags
+                .FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE);
+        final Task[] freeformTasks = new Task[3];
+        final WindowProcessController[] processes = new WindowProcessController[3];
+        for (int i = 0; i < freeformTasks.length; i++) {
+            freeformTasks[i] = new TaskBuilder(mSupervisor)
+                    .setWindowingMode(WINDOWING_MODE_FREEFORM).setCreateActivity(true).build();
+            final ActivityRecord r = freeformTasks[i].getTopMostActivity();
+            r.setState(RESUMED, "test");
+            processes[i] = r.app;
+        }
+        resizeDisplay(mDisplayContent, 2400, 2000);
+        // ---------
+        // | 2 | 1 |
+        // ---------
+        // | 0 |   |
+        // ---------
+        freeformTasks[2].setBounds(0, 0, 1000, 1000);
+        freeformTasks[1].setBounds(1000, 0, 2000, 1000);
+        freeformTasks[0].setBounds(0, 1000, 1000, 2000);
+        mRootWindowContainer.rankTaskLayers();
+        assertEquals(1, freeformTasks[2].mLayerRank);
+        assertEquals(2, freeformTasks[1].mLayerRank);
+        assertEquals(3, freeformTasks[0].mLayerRank);
+        assertFalse("Top doesn't need perceptible hint", (processes[2].getActivityStateFlags()
+                & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+        assertTrue((processes[1].getActivityStateFlags()
+                & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+        assertTrue((processes[0].getActivityStateFlags()
+                & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+
+        // Make task2 occlude half of task0.
+        clearInvocations(mAtm);
+        freeformTasks[2].setBounds(0, 0, 1000, 1500);
+        waitHandlerIdle(mWm.mH);
+        // The process of task0 will demote from perceptible to visible.
+        final int stateFlags0 = processes[0].getActivityStateFlags();
+        assertTrue((stateFlags0
+                & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0);
+        assertFalse((stateFlags0
+                & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+        verify(mAtm).updateOomAdj();
+    }
+
+    @Test
     public void testForceStopPackage() {
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         final ActivityRecord activity = task.getTopMostActivity();