diff options
43 files changed, 800 insertions, 290 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 216bbab882a9..bba21f418e41 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -24820,7 +24820,7 @@ package android.media { method public android.view.Surface getSurface(); method public boolean isPrivacySensitive(); method public void pause() throws java.lang.IllegalStateException; - method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; + method @RequiresPermission(value=android.Manifest.permission.RECORD_AUDIO, conditional=true) public void prepare() throws java.io.IOException, java.lang.IllegalStateException; method public void registerAudioRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioRecordingCallback); method public void release(); method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener); @@ -24861,7 +24861,7 @@ package android.media { method public void setVideoProfile(@NonNull android.media.EncoderProfiles.VideoProfile); method public void setVideoSize(int, int) throws java.lang.IllegalStateException; method public void setVideoSource(int) throws java.lang.IllegalStateException; - method public void start() throws java.lang.IllegalStateException; + method @RequiresPermission(value=android.Manifest.permission.RECORD_AUDIO, conditional=true) public void start() throws java.lang.IllegalStateException; method public void stop() throws java.lang.IllegalStateException; method public void unregisterAudioRecordingCallback(@NonNull android.media.AudioManager.AudioRecordingCallback); field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64 diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index 2e8031dd22fe..2559bd036039 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -231,9 +231,9 @@ public final class ApplicationStartInfo implements Parcelable { public static final int START_COMPONENT_OTHER = 5; /** - * @see #getMonoticCreationTimeMs + * @see #getMonotonicCreationTimeMs */ - private long mMonoticCreationTimeMs; + private long mMonotonicCreationTimeMs; /** * @see #getStartupState @@ -545,8 +545,8 @@ public final class ApplicationStartInfo implements Parcelable { * * @hide */ - public long getMonoticCreationTimeMs() { - return mMonoticCreationTimeMs; + public long getMonotonicCreationTimeMs() { + return mMonotonicCreationTimeMs; } /** @@ -751,14 +751,14 @@ public final class ApplicationStartInfo implements Parcelable { dest.writeParcelable(mStartIntent, flags); dest.writeInt(mLaunchMode); dest.writeBoolean(mWasForceStopped); - dest.writeLong(mMonoticCreationTimeMs); + dest.writeLong(mMonotonicCreationTimeMs); dest.writeInt(mStartComponent); } // LINT.ThenChange(:read_parcel) /** @hide */ public ApplicationStartInfo(long monotonicCreationTimeMs) { - mMonoticCreationTimeMs = monotonicCreationTimeMs; + mMonotonicCreationTimeMs = monotonicCreationTimeMs; } /** @hide */ @@ -776,7 +776,7 @@ public final class ApplicationStartInfo implements Parcelable { mStartIntent = other.mStartIntent; mLaunchMode = other.mLaunchMode; mWasForceStopped = other.mWasForceStopped; - mMonoticCreationTimeMs = other.mMonoticCreationTimeMs; + mMonotonicCreationTimeMs = other.mMonotonicCreationTimeMs; mStartComponent = other.mStartComponent; } @@ -803,7 +803,7 @@ public final class ApplicationStartInfo implements Parcelable { in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class); mLaunchMode = in.readInt(); mWasForceStopped = in.readBoolean(); - mMonoticCreationTimeMs = in.readLong(); + mMonotonicCreationTimeMs = in.readLong(); mStartComponent = in.readInt(); } // LINT.ThenChange(:write_parcel) @@ -887,7 +887,7 @@ public final class ApplicationStartInfo implements Parcelable { } proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode); proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped); - proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonoticCreationTimeMs); + proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonotonicCreationTimeMs); proto.write(ApplicationStartInfoProto.START_COMPONENT, mStartComponent); proto.end(token); } @@ -980,7 +980,7 @@ public final class ApplicationStartInfo implements Parcelable { ApplicationStartInfoProto.WAS_FORCE_STOPPED); break; case (int) ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS: - mMonoticCreationTimeMs = proto.readLong( + mMonotonicCreationTimeMs = proto.readLong( ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS); break; case (int) ApplicationStartInfoProto.START_COMPONENT: @@ -999,7 +999,7 @@ public final class ApplicationStartInfo implements Parcelable { sb.append(prefix) .append("ApplicationStartInfo ").append(seqSuffix).append(':') .append('\n') - .append(" monotonicCreationTimeMs=").append(mMonoticCreationTimeMs) + .append(" monotonicCreationTimeMs=").append(mMonotonicCreationTimeMs) .append('\n') .append(" pid=").append(mPid) .append(" realUid=").append(mRealUid) @@ -1094,7 +1094,7 @@ public final class ApplicationStartInfo implements Parcelable { && TextUtils.equals(mProcessName, o.mProcessName) && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped - && mMonoticCreationTimeMs == o.mMonoticCreationTimeMs + && mMonotonicCreationTimeMs == o.mMonotonicCreationTimeMs && mStartComponent == o.mStartComponent; } @@ -1102,7 +1102,7 @@ public final class ApplicationStartInfo implements Parcelable { public int hashCode() { return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState, mStartType, mLaunchMode, mPackageName, mProcessName, mStartupTimestampsNs, - mMonoticCreationTimeMs, mStartComponent); + mMonotonicCreationTimeMs, mStartComponent); } private boolean timestampsEquals(@NonNull ApplicationStartInfo other) { diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index c6d0f61b529e..8c99bd8e2ed9 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -790,12 +790,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW; } - /** Returns true if the task bounds should persist across power cycles. - * @hide */ - public boolean persistTaskBounds() { - return mWindowingMode == WINDOWING_MODE_FREEFORM; - } - /** * Returns true if the tasks associated with this window configuration are floating. * Floating tasks are laid out differently as they are allowed to extend past the display bounds diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index e431426d5d09..29c84ee00b44 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -170,3 +170,23 @@ flag { description: "Holdback study for jank_perceptible_narrow" bug: "304837972" } + +flag { + namespace: "system_performance" + name: "app_start_info_cleanup_old_records" + description: "Cleanup old records to reduce size of in memory store." + bug: "384539178" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "system_performance" + name: "app_start_info_keep_records_sorted" + description: "Ensure records are kept sorted to avoid extra work" + bug: "384539178" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 6b7b81887706..7e9dfe6d972a 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1030,10 +1030,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation handlePendingControlRequest(statsToken); } else { if (showTypes[0] != 0) { + if ((showTypes[0] & ime()) != 0) { + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_CLIENT_ON_CONTROLS_CHANGED); + } applyAnimation(showTypes[0], true /* show */, false /* fromIme */, false /* skipsCallbacks */, statsToken); } if (hideTypes[0] != 0) { + if ((hideTypes[0] & ime()) != 0) { + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_CLIENT_ON_CONTROLS_CHANGED); + } applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, // The animation of hiding transient types shouldn't be detected by the // app. Otherwise, it might be able to react to the callbacks and cause @@ -1041,6 +1049,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation (hideTypes[0] & ~transientTypes[0]) == 0 /* skipsCallbacks */, statsToken); } + if ((showTypes[0] & ime()) == 0 && (hideTypes[0] & ime()) == 0) { + ImeTracker.forLogging().onCancelled(statsToken, + ImeTracker.PHASE_CLIENT_ON_CONTROLS_CHANGED); + } } } else { if (showTypes[0] != 0) { diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 1b57b0045537..94e9aa709369 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1070,9 +1070,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } if (mSurfacePackage != null) { - mSurfaceControlViewHostParent.detach(); mEmbeddedWindowParams.clear(); if (releaseSurfacePackage) { + mSurfaceControlViewHostParent.detach(); mSurfacePackage.release(); mSurfacePackage = null; } diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index 5dadf32d2a36..b1ba8b32d2f4 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -231,6 +231,7 @@ public interface ImeTracker { PHASE_WM_WINDOW_ANIMATING_TYPES_CHANGED, PHASE_WM_NOTIFY_HIDE_ANIMATION_FINISHED, PHASE_WM_UPDATE_DISPLAY_WINDOW_ANIMATING_TYPES, + PHASE_CLIENT_ON_CONTROLS_CHANGED, }) @Retention(RetentionPolicy.SOURCE) @interface Phase {} @@ -469,6 +470,9 @@ public interface ImeTracker { /** The control target reported its animatingTypes back to WindowManagerService. */ int PHASE_WM_UPDATE_DISPLAY_WINDOW_ANIMATING_TYPES = ImeProtoEnums.PHASE_WM_UPDATE_DISPLAY_WINDOW_ANIMATING_TYPES; + /** InsetsController received a control for the IME. */ + int PHASE_CLIENT_ON_CONTROLS_CHANGED = + ImeProtoEnums.PHASE_CLIENT_ON_CONTROLS_CHANGED; /** * Called when an IME request is started. diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index e125e258c596..c25f6b1dcacb 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -322,8 +322,18 @@ public class Cuj { */ public static final int CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY = 129; + /** + * Track the animation of an ongoing call app back into its status bar chip (displaying the call + * icon and timer) when returning Home. + * + * <p>Tracking starts when the RemoteTransition registered to handle the transition from the app + * to Home is sent the onAnimationStart() signal and start the animation. Tracking ends when + * the animation is fully settled and the transition is complete.</p> + */ + public static final int CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP = 130; + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. - @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY; + @VisibleForTesting static final int LAST_CUJ = CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP; /** @hide */ @IntDef({ @@ -444,7 +454,8 @@ public class Cuj { CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND, CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK, CUJ_DEFAULT_TASK_TO_TASK_ANIMATION, - CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY + CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY, + CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} @@ -576,6 +587,7 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WORK_UTILITY_VIEW_SHRINK; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DEFAULT_TASK_TO_TASK_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DEFAULT_TASK_TO_TASK_ANIMATION; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_RETURN_TO_CALL_CHIP; } private Cuj() { @@ -830,6 +842,8 @@ public class Cuj { return "DEFAULT_TASK_TO_TASK_ANIMATION"; case CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY: return "DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY"; + case CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP: + return "STATUS_BAR_APP_RETURN_TO_CALL_CHIP"; } return "UNKNOWN"; } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 25b9f8ccc6ae..f68afea92850 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -18,6 +18,7 @@ package com.android.wm.shell.shared; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; +import static android.view.Display.INVALID_DISPLAY; import android.annotation.IntDef; import android.app.ActivityManager.RecentTaskInfo; @@ -65,6 +66,11 @@ public class GroupedTaskInfo implements Parcelable { private final int mDeskId; /** + * The ID of the display that desk with [mDeskId] is in. + */ + private final int mDeskDisplayId; + + /** * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or * TYPE_DESK. */ @@ -109,17 +115,19 @@ public class GroupedTaskInfo implements Parcelable { * Create new for a stack of fullscreen tasks */ public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) { - return new GroupedTaskInfo(/* deskId = */ -1, List.of(task), null, TYPE_FULLSCREEN, - /* minimizedFreeformTaskIds = */ null); + return new GroupedTaskInfo(/* deskId = */ -1, /* displayId = */ INVALID_DISPLAY, + List.of(task), null, + TYPE_FULLSCREEN, /* minimizedFreeformTaskIds = */ null); } /** * Create new for a pair of tasks in split screen */ public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1, - @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) { - return new GroupedTaskInfo(/* deskId = */ -1, List.of(task1, task2), splitBounds, - TYPE_SPLIT, /* minimizedFreeformTaskIds = */ null); + @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) { + return new GroupedTaskInfo(/* deskId = */ -1, /* displayId = */ INVALID_DISPLAY, + List.of(task1, task2), + splitBounds, TYPE_SPLIT, /* minimizedFreeformTaskIds = */ null); } /** @@ -127,9 +135,11 @@ public class GroupedTaskInfo implements Parcelable { */ public static GroupedTaskInfo forDeskTasks( int deskId, + int deskDisplayId, @NonNull List<TaskInfo> tasks, @NonNull Set<Integer> minimizedFreeformTaskIds) { - return new GroupedTaskInfo(deskId, tasks, /* splitBounds = */ null, TYPE_DESK, + return new GroupedTaskInfo(deskId, deskDisplayId, tasks, /* splitBounds = */ null, + TYPE_DESK, minimizedFreeformTaskIds.stream().mapToInt(i -> i).toArray()); } @@ -149,11 +159,13 @@ public class GroupedTaskInfo implements Parcelable { private GroupedTaskInfo( int deskId, + int deskDisplayId, @NonNull List<TaskInfo> tasks, @Nullable SplitBounds splitBounds, @GroupType int type, @Nullable int[] minimizedFreeformTaskIds) { mDeskId = deskId; + mDeskDisplayId = deskDisplayId; mTasks = tasks; mGroupedTasks = null; mSplitBounds = splitBounds; @@ -164,6 +176,7 @@ public class GroupedTaskInfo implements Parcelable { private GroupedTaskInfo(@NonNull List<GroupedTaskInfo> groupedTasks) { mDeskId = -1; + mDeskDisplayId = INVALID_DISPLAY; mTasks = null; mGroupedTasks = groupedTasks; mSplitBounds = null; @@ -185,6 +198,7 @@ public class GroupedTaskInfo implements Parcelable { protected GroupedTaskInfo(@NonNull Parcel parcel) { mDeskId = parcel.readInt(); + mDeskDisplayId = parcel.readInt(); mTasks = new ArrayList(); final int numTasks = parcel.readInt(); for (int i = 0; i < numTasks; i++) { @@ -295,6 +309,16 @@ public class GroupedTaskInfo implements Parcelable { } /** + * Returns the ID of the display that hosts the desk represented by [mDeskId]. + */ + public int getDeskDisplayId() { + if (mType != TYPE_DESK) { + throw new IllegalStateException("No display ID for non desktop task"); + } + return mDeskDisplayId; + } + + /** * Get type of this recents entry. One of {@link GroupType}. * Note: This is deprecated, callers should use `isBaseType()` and not make assumptions about * specific group types @@ -323,6 +347,7 @@ public class GroupedTaskInfo implements Parcelable { } GroupedTaskInfo other = (GroupedTaskInfo) obj; return mDeskId == other.mDeskId + && mDeskDisplayId == other.mDeskDisplayId && mType == other.mType && Objects.equals(mTasks, other.mTasks) && Objects.equals(mGroupedTasks, other.mGroupedTasks) @@ -332,7 +357,7 @@ public class GroupedTaskInfo implements Parcelable { @Override public int hashCode() { - return Objects.hash(mDeskId, mType, mTasks, mGroupedTasks, mSplitBounds, + return Objects.hash(mDeskId, mDeskDisplayId, mType, mTasks, mGroupedTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds)); } @@ -345,6 +370,7 @@ public class GroupedTaskInfo implements Parcelable { .collect(Collectors.joining(",\n\t", "[\n\t", "\n]"))); } else { taskString.append("Desk ID= ").append(mDeskId).append(", "); + taskString.append("Desk Display ID=").append(mDeskDisplayId).append(", "); taskString.append("Tasks=" + mTasks.stream() .map(taskInfo -> getTaskInfoDumpString(taskInfo)) .collect(Collectors.joining(", ", "[", "]"))); @@ -377,6 +403,7 @@ public class GroupedTaskInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mDeskId); + parcel.writeInt(mDeskDisplayId); // We don't use the parcel list methods because we want to only write the TaskInfo state // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated final int tasksSize = mTasks != null ? mTasks.size() : 0; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 7f8cfaeb9c03..5e36a102a438 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -332,7 +332,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @Override public void onThresholdCrossed() { - BackAnimationController.this.onThresholdCrossed(); + if (predictiveBackDelayWmTransition()) { + mShellExecutor.execute(BackAnimationController.this::onThresholdCrossed); + } else { + BackAnimationController.this.onThresholdCrossed(); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index bb5b5cec1b4a..382fa9640ff9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -536,17 +536,20 @@ public class RecentTasksController implements TaskStackListenerCallback, } /** - * Represents a desk whose ID is `mDeskId` and contains the tasks in `mDeskTasks`. Some of these - * tasks are minimized and their IDs are contained in the `mMinimizedDeskTasks` set. + * Represents a desk whose ID is `mDeskId` inside the display with `mDisplayId` and contains + * the tasks in `mDeskTasks`. Some of these tasks are minimized and their IDs are contained + * in the `mMinimizedDeskTasks` set. */ private static class Desk { final int mDeskId; + final int mDisplayId; boolean mHasVisibleTasks = false; final ArrayList<TaskInfo> mDeskTasks = new ArrayList<>(); final Set<Integer> mMinimizedDeskTasks = new HashSet<>(); - Desk(int deskId) { + Desk(int deskId, int displayId) { mDeskId = deskId; + mDisplayId = displayId; } void addTask(TaskInfo taskInfo, boolean isMinimized, boolean isVisible) { @@ -562,7 +565,8 @@ public class RecentTasksController implements TaskStackListenerCallback, } GroupedTaskInfo createDeskTaskInfo() { - return GroupedTaskInfo.forDeskTasks(mDeskId, mDeskTasks, mMinimizedDeskTasks); + return GroupedTaskInfo.forDeskTasks(mDeskId, mDisplayId, mDeskTasks, + mMinimizedDeskTasks); } } @@ -601,7 +605,8 @@ public class RecentTasksController implements TaskStackListenerCallback, private Desk getOrCreateDesk(int deskId) { var desk = mTmpDesks.get(deskId); if (desk == null) { - desk = new Desk(deskId); + desk = new Desk(deskId, + mDesktopUserRepositories.get().getCurrent().getDisplayForDesk(deskId)); mTmpDesks.put(deskId, desk); } return desk; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java index 23dfb41d52c1..cca982142a3a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java @@ -153,7 +153,6 @@ public class HomeTransitionObserver implements TransitionObserver, return; } mPendingStartDragTransition = null; - if (aborted) return; if (mPendingHomeVisibilityUpdate != null) { notifyHomeVisibilityChanged(mPendingHomeVisibilityUpdate); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt index eb324f74ca82..238242792782 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt @@ -278,13 +278,16 @@ class MultiDisplayVeiledResizeTaskPositioner( currentDisplayLayout, ) ) - - multiDisplayDragMoveIndicatorController.onDragEnd( - desktopWindowDecoration.mTaskInfo.taskId, - transactionSupplier, - ) } + // Call the MultiDisplayDragMoveIndicatorController to clear any active indicator + // surfaces. This is necessary even if the drag ended on the same display, as surfaces + // may have been created for other displays during the drag. + multiDisplayDragMoveIndicatorController.onDragEnd( + desktopWindowDecoration.mTaskInfo.taskId, + transactionSupplier, + ) + interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt index 75f6bda4d750..4e8812d34ef4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt @@ -21,6 +21,7 @@ import android.app.TaskInfo import android.graphics.Rect import android.os.Parcel import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY import android.window.IWindowContainerToken import android.window.WindowContainerToken import androidx.test.filters.SmallTest @@ -281,7 +282,8 @@ class GroupedTaskInfoTest : ShellTestCase() { val task2 = createTaskInfo(id = 2) val taskInfo = GroupedTaskInfo.forDeskTasks( - /* deskId = */ 500, listOf(task1, task2), setOf()) + /* deskId = */ 500, DEFAULT_DISPLAY, listOf(task1, task2), setOf() + ) assertThat(taskInfo.deskId).isEqualTo(500) assertThat(taskInfo.getTaskById(1)).isEqualTo(task1) @@ -335,6 +337,7 @@ class GroupedTaskInfoTest : ShellTestCase() { ): GroupedTaskInfo { return GroupedTaskInfo.forDeskTasks( deskId, + DEFAULT_DISPLAY, freeformTaskIds.map { createTaskInfo(it) }.toList(), minimizedTaskIds.toSet()) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java index a122c3820dcb..55bff09e0ae2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java @@ -222,7 +222,7 @@ public class HomeTransitionObserverTest extends ShellTestCase { @Test @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX}) - public void startDragToDesktopAborted_doesNotTriggerCallback() throws RemoteException { + public void startDragToDesktopAborted_triggersCallback() throws RemoteException { TransitionInfo info = mock(TransitionInfo.class); TransitionInfo.Change change = mock(TransitionInfo.Change.class); ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); @@ -239,7 +239,7 @@ public class HomeTransitionObserverTest extends ShellTestCase { mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ true); - verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean()); + verify(mListener).onHomeVisibilityChanged(/* isVisible= */ true); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt index 0798613ed632..24a46aacde15 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt @@ -63,6 +63,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoInteractions import org.mockito.Mockito.`when` import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -210,6 +211,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { eq(taskPositioner), ) verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + verifyNoInteractions(mockMultiDisplayDragMoveIndicatorController) } @Test @@ -248,6 +250,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { verify(mockDesktopWindowDecoration, never()).showResizeVeil(any()) verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any()) Assert.assertEquals(rectAfterEnd, endBounds) } @@ -268,6 +271,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { verify(spyDisplayLayout0, never()).localPxToGlobalDp(any(), any()) verify(spyDisplayLayout0, never()).globalDpToLocalPx(any(), any()) + verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any()) } @Test @@ -290,6 +294,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { verify(mockDesktopWindowDecoration, never()).showResizeVeil(any()) verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any()) Assert.assertEquals(rectAfterEnd, endBounds) } @@ -346,6 +351,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { }, eq(taskPositioner), ) + verifyNoInteractions(mockMultiDisplayDragMoveIndicatorController) } @Test diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index f3b21bfdaa3c..3b560b7a880e 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -219,13 +219,14 @@ public final class MediaCodecInfo { private static final int DEFAULT_MAX_SUPPORTED_INSTANCES = 32; private static final int MAX_SUPPORTED_INSTANCES_LIMIT = 256; - private static final class LazyHolder { - private static final Range<Integer> SIZE_RANGE = Process.is64Bit() - ? Range.create(1, 32768) - : Range.create(1, MediaProperties.resolution_limit_32bit().orElse(4096)); - } - private static Range<Integer> getSizeRange() { - return LazyHolder.SIZE_RANGE; + private static Range<Integer> SIZE_RANGE; + private static synchronized Range<Integer> getSizeRange() { + if (SIZE_RANGE == null) { + SIZE_RANGE = Process.is64Bit() + ? Range.create(1, 32768) + : Range.create(1, MediaProperties.resolution_limit_32bit().orElse(4096)); + } + return SIZE_RANGE; } // found stuff that is not supported by framework (=> this should not happen) diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 7af78b81cda5..03bcc6afc1b7 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -1299,6 +1299,7 @@ public class MediaRecorder implements AudioRouting, * start() or before setOutputFormat(). * @throws IOException if prepare fails otherwise. */ + @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true) public void prepare() throws IllegalStateException, IOException { if (mPath != null) { @@ -1337,6 +1338,7 @@ public class MediaRecorder implements AudioRouting, * @throws IllegalStateException if it is called before * prepare() or when the camera is already in use by another app. */ + @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true) public native void start() throws IllegalStateException; /** diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java index be711accd542..89e5372b530e 100644 --- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java +++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java @@ -27,6 +27,7 @@ import android.widget.Button; import android.widget.LinearLayout; import androidx.annotation.GravityInt; +import androidx.annotation.IntDef; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; @@ -34,21 +35,46 @@ import com.android.settingslib.widget.preference.button.R; import com.google.android.material.button.MaterialButton; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A preference handled a button */ public class ButtonPreference extends Preference implements GroupSectionDividerMixin { + public static final int TYPE_FILLED = 0; + public static final int TYPE_TONAL = 1; + public static final int TYPE_OUTLINE = 2; + + @IntDef({TYPE_FILLED, TYPE_TONAL, TYPE_OUTLINE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Type { + } + + public static final int SIZE_NORMAL = 0; + public static final int SIZE_LARGE = 1; + public static final int SIZE_EXTRA_LARGE = 2; + + @IntDef({SIZE_NORMAL, SIZE_LARGE, SIZE_EXTRA_LARGE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Size { + } + enum ButtonStyle { - FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled), - FILLED_LARGE(0, 1, R.layout.settingslib_expressive_button_filled_large), - FILLED_EXTRA(0, 2, R.layout.settingslib_expressive_button_filled_extra), - TONAL_NORMAL(1, 0, R.layout.settingslib_expressive_button_tonal), - TONAL_LARGE(1, 1, R.layout.settingslib_expressive_button_tonal_large), - TONAL_EXTRA(1, 2, R.layout.settingslib_expressive_button_tonal_extra), - OUTLINE_NORMAL(2, 0, R.layout.settingslib_expressive_button_outline), - OUTLINE_LARGE(2, 1, R.layout.settingslib_expressive_button_outline_large), - OUTLINE_EXTRA(2, 2, R.layout.settingslib_expressive_button_outline_extra); + FILLED_NORMAL(TYPE_FILLED, SIZE_NORMAL, R.layout.settingslib_expressive_button_filled), + FILLED_LARGE(TYPE_FILLED, SIZE_LARGE, R.layout.settingslib_expressive_button_filled_large), + FILLED_EXTRA(TYPE_FILLED, SIZE_EXTRA_LARGE, + R.layout.settingslib_expressive_button_filled_extra), + TONAL_NORMAL(TYPE_TONAL, SIZE_NORMAL, R.layout.settingslib_expressive_button_tonal), + TONAL_LARGE(TYPE_TONAL, SIZE_LARGE, R.layout.settingslib_expressive_button_tonal_large), + TONAL_EXTRA(TYPE_TONAL, SIZE_EXTRA_LARGE, + R.layout.settingslib_expressive_button_tonal_extra), + OUTLINE_NORMAL(TYPE_OUTLINE, SIZE_NORMAL, R.layout.settingslib_expressive_button_outline), + OUTLINE_LARGE(TYPE_OUTLINE, SIZE_LARGE, + R.layout.settingslib_expressive_button_outline_large), + OUTLINE_EXTRA(TYPE_OUTLINE, SIZE_EXTRA_LARGE, + R.layout.settingslib_expressive_button_outline_extra); private final int mType; private final int mSize; @@ -60,7 +86,7 @@ public class ButtonPreference extends Preference implements GroupSectionDividerM this.mLayoutId = layoutId; } - static int getLayoutId(int type, int size) { + static int getLayoutId(@Type int type, @Size int size) { for (ButtonStyle style : values()) { if (style.mType == type && style.mSize == size) { return style.mLayoutId; @@ -266,7 +292,7 @@ public class ButtonPreference extends Preference implements GroupSectionDividerM * <li>2: extra large</li> * </ul> */ - public void setButtonStyle(int type, int size) { + public void setButtonStyle(@Type int type, @Size int size) { int layoutId = ButtonStyle.getLayoutId(type, size); setLayoutResource(layoutId); notifyChanged(); diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java index 7f4bebcf4a62..335526632383 100644 --- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java @@ -46,6 +46,8 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; @@ -326,6 +328,15 @@ public abstract class Tile implements Parcelable { return false; } + /** Returns the icon color scheme. */ + @Nullable + public String getIconColorScheme(@NonNull Context context) { + ensureMetadataNotStale(context); + return mMetaData != null + ? mMetaData.getString(TileUtils.META_DATA_PREFERENCE_ICON_COLOR_SCHEME, null) + : null; + } + /** Whether the {@link Activity} should be launched in a separate task. */ public boolean isNewTask() { if (mMetaData != null && mMetaData.containsKey(META_DATA_NEW_TASK)) { diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java index ac0b9b45aba6..d62ed2f60ed0 100644 --- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java @@ -135,6 +135,13 @@ public class TileUtils { public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon"; /** + * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the icon + * color scheme. Only available for preferences on the homepage. + */ + public static final String META_DATA_PREFERENCE_ICON_COLOR_SCHEME = + "com.android.settings.icon_color_scheme"; + + /** * Name of the meta-data item that should be set in the AndroidManifest.xml * to specify the icon background color. The value may or may not be used by Settings app. */ diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt index 84370ed4d2c7..6fb3679dfb7c 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt @@ -43,7 +43,7 @@ fun PlatformTheme(isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Compos val context = LocalContext.current val colorScheme = remember(context, isDarkTheme) { platformColorScheme(isDarkTheme, context) } - val androidColorScheme = remember(context) { AndroidColorScheme(context) } + val androidColorScheme = remember(context, isDarkTheme) { AndroidColorScheme(context) } val typefaceNames = remember(context) { TypefaceNames.get(context) } val typefaceTokens = remember(typefaceNames) { TypefaceTokens(typefaceNames) } val typography = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt index 859137507bbf..358635e2400c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt @@ -80,17 +80,18 @@ class DisplayRepositoryTest : SysuiTestCase() { testScope.backgroundScope, UnconfinedTestDispatcher(), ) - DisplayRepositoryImpl( + val displaysWithDecorRepository = + DisplaysWithDecorationsRepositoryImpl( commandQueue, windowManager, testScope.backgroundScope, displayRepositoryFromLib, ) - .also { - verify(displayManager, never()).registerDisplayListener(any(), any()) - // It needs to be called, just once, for the initial value. - verify(displayManager).getDisplays() - } + DisplayRepositoryImpl(displayRepositoryFromLib, displaysWithDecorRepository).also { + verify(displayManager, never()).registerDisplayListener(any(), any()) + // It needs to be called, just once, for the initial value. + verify(displayManager).getDisplays() + } } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt index 28b9e733be94..bf49d92dd2bd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt @@ -44,9 +44,10 @@ class PerDisplayInstanceRepositoryImplTest : SysuiTestCase() { private val fakeDisplayRepository = kosmos.displayRepository private val fakePerDisplayInstanceProviderWithTeardown = kosmos.fakePerDisplayInstanceProviderWithTeardown + private val lifecycleManager = kosmos.fakeDisplayInstanceLifecycleManager private val underTest: PerDisplayInstanceRepositoryImpl<TestPerDisplayInstance> = - kosmos.fakePerDisplayInstanceRepository + kosmos.createPerDisplayInstanceRepository(overrideLifecycleManager = null) @Before fun addDisplays() = runBlocking { @@ -109,6 +110,43 @@ class PerDisplayInstanceRepositoryImplTest : SysuiTestCase() { verify(kosmos.dumpManager).registerNormalDumpable(anyString(), any()) } + @Test + fun perDisplay_afterCustomLifecycleManagerRemovesDisplay_destroyInstanceInvoked() = + testScope.runTest { + val underTest = + kosmos.createPerDisplayInstanceRepository( + overrideLifecycleManager = lifecycleManager + ) + // Let's start with both + lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID, NON_DEFAULT_DISPLAY_ID) + + val instance = underTest[NON_DEFAULT_DISPLAY_ID] + + lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID) + + // Now that the lifecycle manager says so, let's make sure it was destroyed + assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed) + .containsExactly(instance) + } + + @Test + fun perDisplay_lifecycleManagerDoesNotContainIt_displayRepositoryDoes_returnsNull() = + testScope.runTest { + val underTest = + kosmos.createPerDisplayInstanceRepository( + overrideLifecycleManager = lifecycleManager + ) + // only default display, so getting for the non-default one should fail, despite the + // repository having both displays already + lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID) + + assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isNull() + + lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID, NON_DEFAULT_DISPLAY_ID) + + assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isNotNull() + } + private fun createDisplay(displayId: Int): Display = display(type = Display.TYPE_INTERNAL, id = displayId) diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 14b13d105482..24b955152093 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -286,7 +286,9 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mLaunchSourceId); final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS) .putExtra(Intent.EXTRA_COMPONENT_NAME, - ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()) + .setPackage(mQSSettingsPackageRepository.getSettingsPackageName()); + mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0, mDialogTransitionAnimator.createActivityTransitionController( dialog)); @@ -588,9 +590,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, com.android.internal.R.color.materialColorOnPrimaryContainer)); } text.setText(item.getToolName()); - Intent intent = item.getToolIntent() - .setPackage(mQSSettingsPackageRepository.getSettingsPackageName()); - + Intent intent = item.getToolIntent(); view.setOnClickListener(v -> { final String name = intent.getComponent() != null ? intent.getComponent().flattenToString() diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt index 908d0aafb2b9..02d9c664fcc5 100644 --- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt @@ -33,6 +33,8 @@ import com.android.systemui.display.data.repository.DisplayScopeRepository import com.android.systemui.display.data.repository.DisplayScopeRepositoryImpl import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl +import com.android.systemui.display.data.repository.DisplaysWithDecorationsRepository +import com.android.systemui.display.data.repository.DisplaysWithDecorationsRepositoryImpl import com.android.systemui.display.data.repository.FocusedDisplayRepository import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper @@ -84,6 +86,11 @@ interface DisplayModule { ): DisplayWindowPropertiesRepository @Binds + fun displaysWithDecorationsRepository( + impl: DisplaysWithDecorationsRepositoryImpl + ): DisplaysWithDecorationsRepository + + @Binds fun dumpRegistrationLambda(helper: PerDisplayRepoDumpHelper): PerDisplayRepository.InitCallback @Binds 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 051fe7e5517c..01bbf2d57dd6 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 @@ -16,95 +16,25 @@ package com.android.systemui.display.data.repository -import android.annotation.SuppressLint -import android.view.IWindowManager import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.statusbar.CommandQueue import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.scan -import kotlinx.coroutines.flow.stateIn -/** Repository for providing access to display related information and events. */ -interface DisplayRepository : DisplayRepositoryFromLib { - - /** A [StateFlow] that maintains a set of display IDs that should have system decorations. */ - val displayIdsWithSystemDecorations: StateFlow<Set<Int>> -} +/** + * Repository for providing access to display related information and events. + * + * This is now just an interface that extends [DisplayRepositoryFromLib] to avoid changing all the + * imports in sysui using this interface. + */ +interface DisplayRepository : DisplayRepositoryFromLib, DisplaysWithDecorationsRepository @SysUISingleton -@SuppressLint("SharedFlowCreation") class DisplayRepositoryImpl @Inject constructor( - private val commandQueue: CommandQueue, - private val windowManager: IWindowManager, - @Background bgApplicationScope: CoroutineScope, private val displayRepositoryFromLib: com.android.app.displaylib.DisplayRepository, -) : DisplayRepositoryFromLib by displayRepositoryFromLib, DisplayRepository { - - private val decorationEvents: Flow<Event> = callbackFlow { - val callback = - object : CommandQueue.Callbacks { - override fun onDisplayAddSystemDecorations(displayId: Int) { - trySend(Event.Add(displayId)) - } - - override fun onDisplayRemoveSystemDecorations(displayId: Int) { - trySend(Event.Remove(displayId)) - } - } - commandQueue.addCallback(callback) - awaitClose { commandQueue.removeCallback(callback) } - } - - private val initialDisplayIdsWithDecorations: Set<Int> = - displayIds.value.filter { windowManager.shouldShowSystemDecors(it) }.toSet() - - /** - * A [StateFlow] that maintains a set of display IDs that should have system decorations. - * - * Updates to the set are triggered by: - * - Adding displays via [CommandQueue.Callbacks.onDisplayAddSystemDecorations]. - * - Removing displays via [CommandQueue.Callbacks.onDisplayRemoveSystemDecorations]. - * - Removing displays via [displayRemovalEvent] emissions. - * - * The set is initialized with displays that qualify for system decorations based on - * [WindowManager.shouldShowSystemDecors]. - */ - override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> = - merge(decorationEvents, displayRemovalEvent.map { Event.Remove(it) }) - .scan(initialDisplayIdsWithDecorations) { displayIds: Set<Int>, event: Event -> - when (event) { - is Event.Add -> displayIds + event.displayId - is Event.Remove -> displayIds - event.displayId - } - } - .distinctUntilChanged() - .stateIn( - scope = bgApplicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = initialDisplayIdsWithDecorations, - ) - - private sealed class Event(val displayId: Int) { - class Add(displayId: Int) : Event(displayId) - - class Remove(displayId: Int) : Event(displayId) - } - - private companion object { - const val TAG = "DisplayRepository" - } -} + private val displaysWithDecorationsRepositoryImpl: DisplaysWithDecorationsRepository, +) : + DisplayRepositoryFromLib by displayRepositoryFromLib, + DisplaysWithDecorationsRepository by displaysWithDecorationsRepositoryImpl, + DisplayRepository diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplaysWithDecorationsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplaysWithDecorationsRepository.kt new file mode 100644 index 000000000000..f4a2ed48f295 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplaysWithDecorationsRepository.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2025 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.systemui.display.data.repository + +import android.view.IWindowManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.CommandQueue +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.flow.stateIn + +/** Provides the displays with decorations. */ +interface DisplaysWithDecorationsRepository { + /** A [StateFlow] that maintains a set of display IDs that should have system decorations. */ + val displayIdsWithSystemDecorations: StateFlow<Set<Int>> +} + +@SysUISingleton +class DisplaysWithDecorationsRepositoryImpl +@Inject +constructor( + private val commandQueue: CommandQueue, + private val windowManager: IWindowManager, + @Background bgApplicationScope: CoroutineScope, + displayRepository: com.android.app.displaylib.DisplayRepository, +) : DisplaysWithDecorationsRepository { + + private val decorationEvents: Flow<Event> = callbackFlow { + val callback = + object : CommandQueue.Callbacks { + override fun onDisplayAddSystemDecorations(displayId: Int) { + trySend(Event.Add(displayId)) + } + + override fun onDisplayRemoveSystemDecorations(displayId: Int) { + trySend(Event.Remove(displayId)) + } + } + commandQueue.addCallback(callback) + awaitClose { commandQueue.removeCallback(callback) } + } + + private val initialDisplayIdsWithDecorations: Set<Int> = + displayRepository.displayIds.value + .filter { windowManager.shouldShowSystemDecors(it) } + .toSet() + + /** + * A [StateFlow] that maintains a set of display IDs that should have system decorations. + * + * Updates to the set are triggered by: + * - Adding displays via [CommandQueue.Callbacks.onDisplayAddSystemDecorations]. + * - Removing displays via [CommandQueue.Callbacks.onDisplayRemoveSystemDecorations]. + * - Removing displays via [displayRemovalEvent] emissions. + * + * The set is initialized with displays that qualify for system decorations based on + * [WindowManager.shouldShowSystemDecors]. + */ + override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> = + merge(decorationEvents, displayRepository.displayRemovalEvent.map { Event.Remove(it) }) + .scan(initialDisplayIdsWithDecorations) { displayIds: Set<Int>, event: Event -> + when (event) { + is Event.Add -> displayIds + event.displayId + is Event.Remove -> displayIds - event.displayId + } + } + .distinctUntilChanged() + .stateIn( + scope = bgApplicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = initialDisplayIdsWithDecorations, + ) + + private sealed class Event(val displayId: Int) { + class Add(displayId: Int) : Event(displayId) + + class Remove(displayId: Int) : Event(displayId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 237ec7cfce7f..6cda192c4198 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.TOOL_TYPE_FINGER; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground; +import static com.android.systemui.Flags.predictiveBackDelayWmTransition; import static com.android.systemui.classifier.Classifier.BACK_GESTURE; import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED; @@ -1182,6 +1183,9 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack return; } else if (dx > dy && dx > mTouchSlop) { if (mAllowGesture) { + if (!predictiveBackDelayWmTransition() && mBackAnimation != null) { + mBackAnimation.onThresholdCrossed(); + } if (mBackAnimation == null) { pilferPointers(); } @@ -1197,7 +1201,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack // forward touch mEdgeBackPlugin.onMotionEvent(ev); dispatchToBackAnimation(ev); - if (mBackAnimation != null && mThresholdCrossed && !mLastFrameThresholdCrossed) { + if (predictiveBackDelayWmTransition() && mBackAnimation != null + && mThresholdCrossed && !mLastFrameThresholdCrossed) { mBackAnimation.onThresholdCrossed(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index 922afc7825c4..9cae6c135fb0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -329,6 +329,7 @@ constructor( cookie, component, launchCujType = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + returnCujType = Cuj.CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP, ) { override suspend fun createController( forLaunch: Boolean diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt index 4b516e9c74bc..161e06295852 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt @@ -16,6 +16,8 @@ package com.android.systemui.display.data.repository +import com.android.app.displaylib.DisplayInstanceLifecycleManager +import com.android.app.displaylib.FakeDisplayInstanceLifecycleManager import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl import com.android.systemui.dump.dumpManager @@ -69,13 +71,25 @@ val Kosmos.fakePerDisplayInstanceProviderWithTeardown by Kosmos.Fixture { FakePerDisplayInstanceProviderWithTeardown() } val Kosmos.perDisplayDumpHelper by Kosmos.Fixture { PerDisplayRepoDumpHelper(dumpManager) } +val Kosmos.fakeDisplayInstanceLifecycleManager by + Kosmos.Fixture { FakeDisplayInstanceLifecycleManager() } + val Kosmos.fakePerDisplayInstanceRepository by Kosmos.Fixture { - PerDisplayInstanceRepositoryImpl( - debugName = "fakePerDisplayInstanceRepository", - instanceProvider = fakePerDisplayInstanceProviderWithTeardown, - testScope.backgroundScope, - displayRepository, - perDisplayDumpHelper, - ) + { lifecycleManager: DisplayInstanceLifecycleManager? -> + PerDisplayInstanceRepositoryImpl( + debugName = "fakePerDisplayInstanceRepository", + instanceProvider = fakePerDisplayInstanceProviderWithTeardown, + lifecycleManager, + testScope.backgroundScope, + displayRepository, + perDisplayDumpHelper, + ) + } } + +fun Kosmos.createPerDisplayInstanceRepository( + overrideLifecycleManager: DisplayInstanceLifecycleManager? = null +): PerDisplayInstanceRepositoryImpl<TestPerDisplayInstance> { + return fakePerDisplayInstanceRepository(overrideLifecycleManager) +} diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index f5fb644502ab..2fc9a88a9318 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -366,11 +366,46 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_systemAndLock() throws IOException { - mWallpaperBackupAgent.mIsDeviceInRestore = true; + public void testUpdateWallpaperComponent_immediate_systemAndLock() throws IOException { + mWallpaperBackupAgent.mPackageExists = true; + mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, /* which */ FLAG_LOCK | FLAG_SYSTEM); + assertThat(mWallpaperBackupAgent.mGetPackageMonitorCallCount).isEqualTo(0); + verify(mWallpaperManager, times(1)) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM); + verify(mWallpaperManager, never()) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM); + verify(mWallpaperManager, never()) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK); + verify(mWallpaperManager, never()).clear(anyInt()); + } + + @Test + public void testUpdateWallpaperComponent_immediate_systemOnly() + throws IOException { + mWallpaperBackupAgent.mPackageExists = true; + + mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, + /* which */ FLAG_SYSTEM); + + assertThat(mWallpaperBackupAgent.mGetPackageMonitorCallCount).isEqualTo(0); + verify(mWallpaperManager, times(1)) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM); + verify(mWallpaperManager, never()) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK); + verify(mWallpaperManager, never()) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM); + verify(mWallpaperManager, never()).clear(anyInt()); + } + + @Test + public void testUpdateWallpaperComponent_delayed_systemAndLock() throws IOException { + mWallpaperBackupAgent.mIsDeviceInRestore = true; + + mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, + /* which */ FLAG_LOCK | FLAG_SYSTEM); // Imitate wallpaper component installation. mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, /* uid */0); @@ -384,13 +419,12 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_systemOnly() + public void testUpdateWallpaperComponent_delayed_systemOnly() throws IOException { mWallpaperBackupAgent.mIsDeviceInRestore = true; mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, /* which */ FLAG_SYSTEM); - // Imitate wallpaper component installation. mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, /* uid */0); @@ -405,7 +439,7 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_deviceNotInRestore_doesNotApply() + public void testUpdateWallpaperComponent_delayed_deviceNotInRestore_doesNotApply() throws IOException { mWallpaperBackupAgent.mIsDeviceInRestore = false; @@ -421,7 +455,7 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_differentPackageInstalled_doesNotApply() + public void testUpdateWallpaperComponent_delayed_differentPackageInstalled_doesNotApply() throws IOException { mWallpaperBackupAgent.mIsDeviceInRestore = false; @@ -745,9 +779,8 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_delayedRestore_logsSuccess() throws Exception { + public void testUpdateWallpaperComponent_delayed_succeeds_logsSuccess() throws Exception { mWallpaperBackupAgent.mIsDeviceInRestore = true; - when(mWallpaperManager.setWallpaperComponent(any())).thenReturn(true); when(mWallpaperManager.setWallpaperComponentWithFlags(any(), eq(FLAG_LOCK | FLAG_SYSTEM))) .thenReturn(true); BackupRestoreEventLogger logger = new BackupRestoreEventLogger( @@ -771,9 +804,8 @@ public class WallpaperBackupAgentTest { @Test - public void testUpdateWallpaperComponent_delayedRestoreFails_logsFailure() throws Exception { + public void testUpdateWallpaperComponent_delayed_fails_logsFailure() throws Exception { mWallpaperBackupAgent.mIsDeviceInRestore = true; - when(mWallpaperManager.setWallpaperComponent(any())).thenReturn(false); BackupRestoreEventLogger logger = new BackupRestoreEventLogger( BackupAnnotations.OperationType.RESTORE); when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger); @@ -793,7 +825,7 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_delayedRestore_packageNotInstalled_logsFailure() + public void testUpdateWallpaperComponent_delayed_packageNotInstalled_logsFailure() throws Exception { mWallpaperBackupAgent.mIsDeviceInRestore = false; BackupRestoreEventLogger logger = new BackupRestoreEventLogger( @@ -990,6 +1022,8 @@ public class WallpaperBackupAgentTest { List<File> mBackedUpFiles = new ArrayList<>(); PackageMonitor mWallpaperPackageMonitor; boolean mIsDeviceInRestore = false; + boolean mPackageExists = false; + int mGetPackageMonitorCallCount = 0; @Override protected void backupFile(File file, FullBackupDataOutput data) { @@ -998,7 +1032,7 @@ public class WallpaperBackupAgentTest { @Override boolean servicePackageExists(ComponentName comp) { - return false; + return mPackageExists; } @Override @@ -1008,6 +1042,7 @@ public class WallpaperBackupAgentTest { @Override PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) { + mGetPackageMonitorCallCount++; mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, which); return mWallpaperPackageMonitor; } diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 517279bd7527..8b3eb48fc783 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -98,6 +98,9 @@ public final class AppStartInfoTracker { @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16; + @VisibleForTesting + static final long APP_START_INFO_HISTORY_LENGTH_MS = TimeUnit.DAYS.toMillis(14); + /** * The max number of records that can be present in {@link mInProgressRecords}. * @@ -120,9 +123,13 @@ public final class AppStartInfoTracker { * Monotonic clock which does not reset on reboot. * * Time for offset is persisted along with records, see {@link #persistProcessStartInfo}. - * This does not follow the recommendation of {@link MonotonicClock} to persist on shutdown as - * it's ok in this case to lose any time change past the last persist as records added since - * then will be lost as well and the purpose of this clock is to keep records in order. + * This does not currently follow the recommendation of {@link MonotonicClock} to persist on + * shutdown as it's ok in this case to lose any time change past the last persist as records + * added since then will be lost as well. Since this time is used for cleanup as well, the + * potential old offset may result in the cleanup window being extended slightly beyond the + * targeted 14 days. + * + * TODO: b/402794215 - Persist on shutdown once persist performance is sufficiently improved. */ @VisibleForTesting MonotonicClock mMonotonicClock = null; @@ -296,7 +303,7 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime()); + ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); start.setIntent(intent); start.setStartType(ApplicationStartInfo.START_TYPE_UNSET); @@ -454,7 +461,7 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime()); + ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); addBaseFieldsFromProcessRecord(start, app); start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); start.addStartupTimestamp( @@ -484,7 +491,7 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime()); + ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); addBaseFieldsFromProcessRecord(start, app); start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); start.addStartupTimestamp( @@ -511,7 +518,7 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime()); + ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); addBaseFieldsFromProcessRecord(start, app); start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); start.addStartupTimestamp( @@ -533,7 +540,7 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime()); + ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); addBaseFieldsFromProcessRecord(start, app); start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); start.addStartupTimestamp( @@ -721,8 +728,8 @@ public final class AppStartInfoTracker { Collections.sort( list, (a, b) -> - Long.compare(b.getMonoticCreationTimeMs(), - a.getMonoticCreationTimeMs())); + Long.compare(b.getMonotonicCreationTimeMs(), + a.getMonotonicCreationTimeMs())); int size = list.size(); if (maxNum > 0) { size = Math.min(size, maxNum); @@ -1098,7 +1105,7 @@ public final class AppStartInfoTracker { mLastAppStartInfoPersistTimestamp = now; } } - proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTime()); + proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTimeMs()); if (succeeded) { proto.flush(); af.finishWrite(out); @@ -1219,7 +1226,11 @@ public final class AppStartInfoTracker { } } - private long getMonotonicTime() { + /** + * Monotonic time that doesn't change with reboot or device time change for ordering records. + */ + @VisibleForTesting + public long getMonotonicTimeMs() { if (mMonotonicClock == null) { // This should never happen. Return 0 to not interfere with past or future records. return 0; @@ -1229,7 +1240,7 @@ public final class AppStartInfoTracker { /** A container class of (@link android.app.ApplicationStartInfo) */ final class AppStartInfoContainer { - private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp. + private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by monotonic time. private int mMaxCapacity; private int mUid; private boolean mMonitoringModeEnabled = false; @@ -1260,9 +1271,12 @@ public final class AppStartInfoTracker { return; } - // Sort records so we can remove the least recent ones. - Collections.sort(mInfos, (a, b) -> - Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs())); + if (!android.app.Flags.appStartInfoKeepRecordsSorted()) { + // Sort records so we can remove the least recent ones. + Collections.sort(mInfos, (a, b) -> + Long.compare(b.getMonotonicCreationTimeMs(), + a.getMonotonicCreationTimeMs())); + } // Remove records and trim list object back to size. mInfos.subList(0, mInfos.size() - getMaxCapacity()).clear(); @@ -1277,25 +1291,34 @@ public final class AppStartInfoTracker { @GuardedBy("mLock") void addStartInfoLocked(ApplicationStartInfo info) { - int size = mInfos.size(); - if (size >= getMaxCapacity()) { - // Remove oldest record if size is over max capacity. - int oldestIndex = -1; - long oldestTimeStamp = Long.MAX_VALUE; - for (int i = 0; i < size; i++) { - ApplicationStartInfo startInfo = mInfos.get(i); - if (startInfo.getMonoticCreationTimeMs() < oldestTimeStamp) { - oldestTimeStamp = startInfo.getMonoticCreationTimeMs(); - oldestIndex = i; - } + if (android.app.Flags.appStartInfoKeepRecordsSorted()) { + while (mInfos.size() >= getMaxCapacity()) { + // Expected to execute at most once. + mInfos.removeLast(); } - if (oldestIndex >= 0) { - mInfos.remove(oldestIndex); + mInfos.addFirst(info); + } else { + int size = mInfos.size(); + if (size >= getMaxCapacity()) { + // Remove oldest record if size is over max capacity. + int oldestIndex = -1; + long oldestTimeStamp = Long.MAX_VALUE; + for (int i = 0; i < size; i++) { + ApplicationStartInfo startInfo = mInfos.get(i); + if (startInfo.getMonotonicCreationTimeMs() < oldestTimeStamp) { + oldestTimeStamp = startInfo.getMonotonicCreationTimeMs(); + oldestIndex = i; + } + } + if (oldestIndex >= 0) { + mInfos.remove(oldestIndex); + } } + mInfos.add(info); + Collections.sort(mInfos, (a, b) -> + Long.compare(b.getMonotonicCreationTimeMs(), + a.getMonotonicCreationTimeMs())); } - mInfos.add(info); - Collections.sort(mInfos, (a, b) -> - Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs())); } /** @@ -1439,9 +1462,25 @@ public final class AppStartInfoTracker { long token = proto.start(fieldId); proto.write(AppsStartInfoProto.Package.User.UID, mUid); int size = mInfos.size(); - for (int i = 0; i < size; i++) { - mInfos.get(i).writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO, - byteArrayOutputStream, objectOutputStream, typedXmlSerializer); + if (android.app.Flags.appStartInfoCleanupOldRecords()) { + long removeOlderThan = getMonotonicTimeMs() - APP_START_INFO_HISTORY_LENGTH_MS; + // Iterate backwards so we can remove old records as we go. + for (int i = size - 1; i >= 0; i--) { + if (mInfos.get(i).getMonotonicCreationTimeMs() < removeOlderThan) { + // Remove the record. + mInfos.remove(i); + } else { + mInfos.get(i).writeToProto( + proto, AppsStartInfoProto.Package.User.APP_START_INFO, + byteArrayOutputStream, objectOutputStream, typedXmlSerializer); + } + } + } else { + for (int i = 0; i < size; i++) { + mInfos.get(i).writeToProto( + proto, AppsStartInfoProto.Package.User.APP_START_INFO, + byteArrayOutputStream, objectOutputStream, typedXmlSerializer); + } } proto.write(AppsStartInfoProto.Package.User.MONITORING_ENABLED, mMonitoringModeEnabled); proto.end(token); @@ -1466,7 +1505,13 @@ public final class AppStartInfoTracker { info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO, byteArrayInputStream, objectInputStream, typedXmlPullParser); info.setPackageName(packageName); - mInfos.add(info); + if (android.app.Flags.appStartInfoKeepRecordsSorted()) { + // Since the writes are done from oldest to newest, each additional + // record will be newer than the previous so use addFirst. + mInfos.addFirst(info); + } else { + mInfos.add(info); + } break; case (int) AppsStartInfoProto.Package.User.MONITORING_ENABLED: mMonitoringModeEnabled = proto.readBoolean( diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 6e6d00d62819..d9db178e0dc2 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1230,7 +1230,7 @@ public class InputManagerService extends IInputManager.Stub "registerTabletModeChangedListener()")) { throw new SecurityException("Requires TABLET_MODE_LISTENER permission"); } - Objects.requireNonNull(listener, "listener must not be null"); + Objects.requireNonNull(listener, "event must not be null"); synchronized (mTabletModeLock) { final int callingPid = Binder.getCallingPid(); @@ -1342,7 +1342,7 @@ public class InputManagerService extends IInputManager.Stub @Override public void requestPointerCapture(IBinder inputChannelToken, boolean enabled) { - Objects.requireNonNull(inputChannelToken, "inputChannelToken must not be null"); + Objects.requireNonNull(inputChannelToken, "event must not be null"); mNative.requestPointerCapture(inputChannelToken, enabled); } diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java index f5daa8036726..8c3b7c606f04 100644 --- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java +++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java @@ -17,11 +17,13 @@ package com.android.server.security.advancedprotection; import static android.provider.Settings.Secure.ADVANCED_PROTECTION_MODE; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.Manifest; import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.StatsManager; import android.content.Context; import android.content.SharedPreferences; import android.os.Binder; @@ -45,6 +47,7 @@ import android.security.advancedprotection.IAdvancedProtectionService; import android.security.advancedprotection.AdvancedProtectionProtoEnums; import android.util.ArrayMap; import android.util.Slog; +import android.util.StatsEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; @@ -137,6 +140,15 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub mProviders.add(new DisallowWepAdvancedProtectionProvider()); } + private void initLogging() { + StatsManager statsManager = mContext.getSystemService(StatsManager.class); + statsManager.setPullAtomCallback( + FrameworkStatsLog.ADVANCED_PROTECTION_STATE_INFO, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, + new AdvancedProtectionStatePullAtomCallback()); + } + // Only for tests @VisibleForTesting AdvancedProtectionService(@NonNull Context context, @NonNull AdvancedProtectionStore store, @@ -399,6 +411,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub Slog.i(TAG, "Advanced protection is enabled"); } mService.initFeatures(enabled); + mService.initLogging(); } } } @@ -500,4 +513,22 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub } } } + + private class AdvancedProtectionStatePullAtomCallback + implements StatsManager.StatsPullAtomCallback { + + @Override + public int onPullAtom(int atomTag, List<StatsEvent> data) { + if (atomTag != FrameworkStatsLog.ADVANCED_PROTECTION_STATE_INFO) { + return StatsManager.PULL_SKIP; + } + + data.add( + FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.ADVANCED_PROTECTION_STATE_INFO, + /*enabled*/ isAdvancedProtectionEnabledInternal(), + /*hours_since_enabled*/ hoursSinceLastChange())); + return StatsManager.PULL_SUCCESS; + } + } } diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java index a731bf7c64b3..70fc6bace868 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java +++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java @@ -82,6 +82,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, */ @VisibleForTesting static final int SNAPSHOT_MODE_NONE = 2; + static final float THEME_SNAPSHOT_MIN_Length = 128.0f; protected final WindowManagerService mService; protected final float mHighResSnapshotScale; @@ -436,14 +437,21 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, final Rect taskBounds = source.getBounds(); final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride(); final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState); + final int taskWidth = taskBounds.width(); + final int taskHeight = taskBounds.height(); + float scale = mHighResSnapshotScale; + if (Flags.reduceTaskSnapshotMemoryUsage()) { + final int minLength = Math.min(taskWidth, taskHeight); + if (THEME_SNAPSHOT_MIN_Length < minLength) { + scale = Math.min(THEME_SNAPSHOT_MIN_Length / minLength, scale); + } + } final SnapshotDrawerUtils.SystemBarBackgroundPainter decorPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags, attrs.privateFlags, attrs.insetsFlags.appearance, taskDescription, - mHighResSnapshotScale, mainWindow.getRequestedVisibleTypes()); - final int taskWidth = taskBounds.width(); - final int taskHeight = taskBounds.height(); - final int width = (int) (taskWidth * mHighResSnapshotScale); - final int height = (int) (taskHeight * mHighResSnapshotScale); + scale, mainWindow.getRequestedVisibleTypes()); + final int width = (int) (taskWidth * scale); + final int height = (int) (taskHeight * scale); final RenderNode node = RenderNode.create("SnapshotController", null); node.setLeftTopRightBottom(0, 0, width, height); node.setClipToBounds(false); diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java index fee5566af484..d8087265c1d3 100644 --- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java +++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java @@ -69,11 +69,11 @@ public class DesktopAppCompatAspectRatioPolicy { * Calculates the final aspect ratio of an launching activity based on the task it will be * launched in. Takes into account any min or max aspect ratio constraints. */ - float calculateAspectRatio(@NonNull Task task) { + float calculateAspectRatio(@NonNull Task task, boolean hasOrientationMismatch) { final float maxAspectRatio = getMaxAspectRatio(); final float minAspectRatio = getMinAspectRatio(task); float desiredAspectRatio = 0; - desiredAspectRatio = getDesiredAspectRatio(task); + desiredAspectRatio = getDesiredAspectRatio(task, hasOrientationMismatch); if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) { desiredAspectRatio = maxAspectRatio; } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) { @@ -87,13 +87,14 @@ public class DesktopAppCompatAspectRatioPolicy { * any min or max aspect ratio constraints. */ @VisibleForTesting - float getDesiredAspectRatio(@NonNull Task task) { + float getDesiredAspectRatio(@NonNull Task task, boolean hasOrientationMismatch) { final float letterboxAspectRatioOverride = getFixedOrientationLetterboxAspectRatio(task); // Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will // be respected in #calculateAspectRatio. if (isDefaultMultiWindowLetterboxAspectRatioDesired(task)) { return DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW; - } else if (letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) { + } else if (hasOrientationMismatch + && letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) { return letterboxAspectRatioOverride; } return AppCompatUtils.computeAspectRatio(task.getDisplayArea().getBounds()); diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java index d9354323ae7c..83ca5f6f83f4 100644 --- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java +++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.isFixedOrientation; import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; import static android.content.pm.ActivityInfo.isFixedOrientationPortrait; @@ -32,8 +31,8 @@ import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; -import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.pm.ActivityInfo.WindowLayout; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.SystemProperties; import android.util.Size; @@ -152,19 +151,25 @@ public final class DesktopModeBoundsCalculator { } final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy = activity.mAppCompatController.getDesktopAspectRatioPolicy(); - float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(task); + final int stableBoundsOrientation = stableBounds.height() >= stableBounds.width() + ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + int activityOrientation = getActivityConfigurationOrientation( + activity, task, stableBoundsOrientation); + // Use orientation mismatch to resolve aspect ratio to match fixed orientation letterboxing + // policy in {@link ActivityRecord.resolveFixedOrientationConfiguration} + final boolean hasOrientationMismatch = stableBoundsOrientation != activityOrientation; + float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio( + task, hasOrientationMismatch); final float tdaWidth = stableBounds.width(); final float tdaHeight = stableBounds.height(); - final int taskConfigOrientation = task.getConfiguration().orientation; - final int activityOrientation = getActivityOrientation(activity, task); - final Size initialSize = switch (taskConfigOrientation) { + final Size initialSize = switch (stableBoundsOrientation) { case ORIENTATION_LANDSCAPE -> { // Device in landscape orientation. if (appAspectRatio == 0) { appAspectRatio = 1; } if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) { - if (isFixedOrientationPortrait(activityOrientation)) { + if (hasOrientationMismatch) { // For portrait resizeable activities, respect apps fullscreen width but // apply ideal size height. yield new Size((int) ((tdaHeight / appAspectRatio) + 0.5f), @@ -183,7 +188,7 @@ public final class DesktopModeBoundsCalculator { final int customPortraitWidthForLandscapeApp = screenBounds.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2); if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) { - if (isFixedOrientationLandscape(activityOrientation)) { + if (hasOrientationMismatch) { if (appAspectRatio == 0) { appAspectRatio = tdaWidth / (tdaWidth - 1); } @@ -198,7 +203,7 @@ public final class DesktopModeBoundsCalculator { if (appAspectRatio == 0) { appAspectRatio = 1; } - if (isFixedOrientationLandscape(activityOrientation)) { + if (hasOrientationMismatch) { // For landscape unresizeable activities, apply custom app width to ideal size // and calculate maximum size with this area while maintaining original aspect // ratio. @@ -230,19 +235,23 @@ public final class DesktopModeBoundsCalculator { && !desktopAppCompatAspectRatioPolicy.hasMinAspectRatioOverride(task); } - private static @ScreenOrientation int getActivityOrientation( - @NonNull ActivityRecord activity, @NonNull Task task) { + private static @Configuration.Orientation int getActivityConfigurationOrientation( + @NonNull ActivityRecord activity, @NonNull Task task, + @Configuration.Orientation int stableBoundsOrientation) { final int activityOrientation = activity.getOverrideOrientation(); final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy = activity.mAppCompatController.getDesktopAspectRatioPolicy(); - if (desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task) + if ((desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task) && (!isFixedOrientation(activityOrientation) - || activityOrientation == SCREEN_ORIENTATION_LOCKED)) { + || activityOrientation == SCREEN_ORIENTATION_LOCKED)) + || isFixedOrientationPortrait(activityOrientation)) { // If a user aspect ratio override should be applied, treat the activity as portrait if // it has not specified a fix orientation. - return SCREEN_ORIENTATION_PORTRAIT; + return ORIENTATION_PORTRAIT; } - return activityOrientation; + // If activity orientation is undefined inherit task orientation. + return isFixedOrientationLandscape(activityOrientation) + ? ORIENTATION_LANDSCAPE : stableBoundsOrientation; } /** @@ -252,7 +261,7 @@ public final class DesktopModeBoundsCalculator { // TODO(b/400617906): Merge duplicate initial bounds calculations to shared class. @NonNull private static Size maximizeSizeGivenAspectRatio( - @ScreenOrientation int orientation, + @Configuration.Orientation int orientation, @NonNull Size targetArea, float aspectRatio, int captionHeight @@ -261,7 +270,7 @@ public final class DesktopModeBoundsCalculator { final int targetWidth = targetArea.getWidth(); final int finalHeight; final int finalWidth; - if (isFixedOrientationPortrait(orientation)) { + if (orientation == ORIENTATION_PORTRAIT) { // Portrait activity. // Calculate required width given ideal height and aspect ratio. int tempWidth = (int) (targetHeight / aspectRatio); diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java index eafc8be7bf77..016cebac2b86 100644 --- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java +++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java @@ -24,6 +24,10 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.graphics.Bitmap; +import android.graphics.PixelFormat; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.ImageReader; import android.os.Process; import android.os.SystemClock; import android.os.Trace; @@ -33,10 +37,12 @@ import android.window.TaskSnapshot; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.TransitionAnimation; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto; +import com.android.window.flags.Flags; import java.io.File; import java.io.FileOutputStream; @@ -400,23 +406,20 @@ class SnapshotPersistQueue { Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mId); return false; } - final Bitmap bitmap = Bitmap.wrapHardwareBuffer( - mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace()); - if (bitmap == null) { - Slog.e(TAG, "Invalid task snapshot hw bitmap"); - return false; - } - final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */); + final HardwareBuffer hwBuffer = mSnapshot.getHardwareBuffer(); + final int width = hwBuffer.getWidth(); + final int height = hwBuffer.getHeight(); + final int pixelFormat = hwBuffer.getFormat(); + final Bitmap swBitmap = !Flags.reduceTaskSnapshotMemoryUsage() + || (pixelFormat != PixelFormat.RGB_565 && pixelFormat != PixelFormat.RGBA_8888) + || !mSnapshot.isRealSnapshot() + || TransitionAnimation.hasProtectedContent(hwBuffer) + ? copyToSwBitmapReadBack() + : copyToSwBitmapDirect(width, height, pixelFormat); if (swBitmap == null) { - Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + ", isMutable=" - + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed."); return false; } - final int width = bitmap.getWidth(); - final int height = bitmap.getHeight(); - bitmap.recycle(); - final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId); try (FileOutputStream fos = new FileOutputStream(file)) { swBitmap.compress(JPEG, COMPRESS_QUALITY, fos); @@ -448,6 +451,58 @@ class SnapshotPersistQueue { return true; } + private Bitmap copyToSwBitmapReadBack() { + final Bitmap bitmap = Bitmap.wrapHardwareBuffer( + mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace()); + if (bitmap == null) { + Slog.e(TAG, "Invalid task snapshot hw bitmap"); + return null; + } + + final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */); + if (swBitmap == null) { + Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + + ", isMutable=" + bitmap.isMutable() + + ") to (config=ARGB_8888, isMutable=false) failed."); + return null; + } + bitmap.recycle(); + return swBitmap; + } + + /** + * Use ImageReader to create the software bitmap, so SkImage won't create an extra texture. + */ + private Bitmap copyToSwBitmapDirect(int width, int height, int pixelFormat) { + try (ImageReader ir = ImageReader.newInstance(width, height, + pixelFormat, 1 /* maxImages */)) { + ir.getSurface().attachAndQueueBufferWithColorSpace(mSnapshot.getHardwareBuffer(), + mSnapshot.getColorSpace()); + try (Image image = ir.acquireLatestImage()) { + if (image == null || image.getPlaneCount() < 1) { + Slog.e(TAG, "Image reader cannot acquire image"); + return null; + } + + final Image.Plane[] planes = image.getPlanes(); + if (planes.length != 1) { + Slog.e(TAG, "Image reader cannot get plane"); + return null; + } + final Image.Plane plane = planes[0]; + final int rowPadding = plane.getRowStride() - plane.getPixelStride() + * image.getWidth(); + final Bitmap swBitmap = Bitmap.createBitmap( + image.getWidth() + rowPadding / plane.getPixelStride() /* width */, + image.getHeight() /* height */, + pixelFormat == PixelFormat.RGB_565 + ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888); + swBitmap.copyPixelsFromBuffer(plane.getBuffer()); + return swBitmap; + } + } + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8587b5a9c7ca..0531828be6d4 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1832,6 +1832,17 @@ class Task extends TaskFragment { && supportsMultiWindowInDisplayArea(tda); } + /** Returns true if the task bounds should persist across power cycles. */ + private static boolean persistTaskBounds(@NonNull WindowConfiguration configuration) { + return configuration.getWindowingMode() == WINDOWING_MODE_FREEFORM; + } + + /** Returns true if the nested task is allowed to have independent bounds from its parent. */ + private static boolean allowIndependentBoundsFromParent( + @NonNull WindowConfiguration configuration) { + return configuration.getWindowingMode() == WINDOWING_MODE_FREEFORM; + } + /** * Check whether this task can be launched on the specified display. * @@ -1996,11 +2007,11 @@ class Task extends TaskFragment { private void onConfigurationChangedInner(Configuration newParentConfig) { // Check if the new configuration supports persistent bounds (eg. is Freeform) and if so // restore the last recorded non-fullscreen bounds. - final boolean prevPersistTaskBounds = getWindowConfiguration().persistTaskBounds(); - boolean nextPersistTaskBounds = - getRequestedOverrideConfiguration().windowConfiguration.persistTaskBounds(); + final boolean prevPersistTaskBounds = persistTaskBounds(getWindowConfiguration()); + boolean nextPersistTaskBounds = persistTaskBounds( + getRequestedOverrideConfiguration().windowConfiguration); if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_UNDEFINED) { - nextPersistTaskBounds = newParentConfig.windowConfiguration.persistTaskBounds(); + nextPersistTaskBounds = persistTaskBounds(newParentConfig.windowConfiguration); } // Only restore to the last non-fullscreen bounds when the requested override bounds // have not been explicitly set already. @@ -2042,7 +2053,7 @@ class Task extends TaskFragment { // If the configuration supports persistent bounds (eg. Freeform), keep track of the // current (non-fullscreen) bounds for persistence. - if (getWindowConfiguration().persistTaskBounds()) { + if (persistTaskBounds(getWindowConfiguration())) { final Rect currentBounds = getRequestedOverrideBounds(); if (!currentBounds.isEmpty()) { setLastNonFullscreenBounds(currentBounds); @@ -2383,33 +2394,48 @@ class Task extends TaskFragment { } void updateOverrideConfigurationFromLaunchBounds() { - // If the task is controlled by another organized task, do not set override - // configurations and let its parent (organized task) to control it; final Task rootTask = getRootTask(); - boolean shouldInheritBounds = rootTask != this && rootTask.isOrganized(); - if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) { - // Only inherit from organized parent when this task is not organized. - shouldInheritBounds &= !isOrganized(); + final boolean hasParentTask = rootTask != this; + final int windowingMode = getWindowingMode(); + final boolean isNonStandardOrFullscreen = !isActivityTypeStandardOrUndefined() + || windowingMode == WINDOWING_MODE_FULLSCREEN; + if (!Flags.nestedTasksWithIndependentBounds() + && !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) { + final Rect bounds; + if (hasParentTask && rootTask.isOrganized()) { + bounds = null; + } else if (isNonStandardOrFullscreen) { + bounds = isResizeable() ? rootTask.getRequestedOverrideBounds() : null; + } else if (!persistTaskBounds(getWindowConfiguration())) { + bounds = rootTask.getRequestedOverrideBounds(); + } else { + bounds = mLastNonFullscreenBounds; + } + setBounds(bounds); + return; } - final Rect bounds = shouldInheritBounds ? null : getLaunchBounds(); - setBounds(bounds); - } - /** Returns the bounds that should be used to launch this task. */ - Rect getLaunchBounds() { - final Task rootTask = getRootTask(); - if (rootTask == null) { - return null; + // Non-standard/fullscreen unresizable tasks should always inherit. + boolean shouldInheritBounds = isNonStandardOrFullscreen && !isResizeable(); + // Task itself is not organized (e.g. Home), just inherit from its organized parent. + shouldInheritBounds |= hasParentTask && rootTask.isOrganized() && !isOrganized(); + // Nested tasks should inherit when they're not allowed to have independent bounds, such as + // in multi-window split-screen. + shouldInheritBounds |= hasParentTask + && !(allowIndependentBoundsFromParent(getWindowConfiguration()) + && persistTaskBounds(getWindowConfiguration())); + if (shouldInheritBounds) { + setBounds(null); + return; } - - final int windowingMode = getWindowingMode(); - if (!isActivityTypeStandardOrUndefined() - || windowingMode == WINDOWING_MODE_FULLSCREEN) { - return isResizeable() ? rootTask.getRequestedOverrideBounds() : null; - } else if (!getWindowConfiguration().persistTaskBounds()) { - return rootTask.getRequestedOverrideBounds(); + if (!hasParentTask && !persistTaskBounds(getWindowConfiguration())) { + // Non-nested, non-persistable tasks such as PIP or multi-window floating windows. + return; } - return mLastNonFullscreenBounds; + // Non-nested, persisted tasks (e.g. top-level freeform) or nested persisted tasks that + // allow independent bounds from parent (e.g. nested freeform) should use launch-params + // bounds set to |mLastNonFullscreenBounds|. + setBounds(mLastNonFullscreenBounds); } void setRootProcess(WindowProcessController proc) { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java index 987b9c6427d8..3289d70b89ac 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java @@ -680,6 +680,67 @@ public class ApplicationStartInfoTest { ApplicationStartInfo.START_TIMESTAMP_FORK)); } + /** + * Test that cleanup old records works as expected, removing records that are older than the max + * retention length. + */ + @Test + @EnableFlags(android.app.Flags.FLAG_APP_START_INFO_CLEANUP_OLD_RECORDS) + public void testOldRecordsCleanup() throws Exception { + // Use a different start timestamp for each record so we can identify which was removed. + // This timestamp is not used for ordering and has no impact on removal. + final long startTimeRecord1 = 123L; + final long startTimeRecord2 = 456L; + final long startTimeRecord3 = 789L; + + // Create a process record to use with all starts. + ProcessRecord app = makeProcessRecord( + APP_1_PID_1, // pid + APP_1_UID, // uid + APP_1_UID, // packageUid + null, // definingUid + APP_1_PROCESS_NAME, // processName + APP_1_PACKAGE_NAME); // packageName + + // Set monotonic time to 1, and then trigger a start info record. + doReturn(1L).when(mAppStartInfoTracker).getMonotonicTimeMs(); + mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord1, app, + buildIntent(COMPONENT), false /* isAlarm */); + + // Set monotonic time to 2, and then trigger another start info record. + doReturn(2L).when(mAppStartInfoTracker).getMonotonicTimeMs(); + mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord2, app, + buildIntent(COMPONENT), false /* isAlarm */); + + // Set monotonic time to 3, then trigger another start info record. + doReturn(3L).when(mAppStartInfoTracker).getMonotonicTimeMs(); + mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord3, app, + buildIntent(COMPONENT), false /* isAlarm */); + + // Verify that all 3 records were added successfully. + ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>(); + mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list); + assertEquals(3, list.size()); + assertEquals(startTimeRecord3, list.get(0).getStartupTimestamps().get(0).longValue()); + assertEquals(startTimeRecord2, list.get(1).getStartupTimestamps().get(0).longValue()); + assertEquals(startTimeRecord1, list.get(2).getStartupTimestamps().get(0).longValue()); + + // Set monotonic time to max history length + 3 so that the older 2 records will be removed + // but that newest 1 will remain. + doReturn(AppStartInfoTracker.APP_START_INFO_HISTORY_LENGTH_MS + 3L) + .when(mAppStartInfoTracker).getMonotonicTimeMs(); + + // Trigger a persist which will trigger the cleanup of old records. + mAppStartInfoTracker.persistProcessStartInfo(); + + // Now verify that the records older than max were removed, and that the records not older + // remain. + list.clear(); + mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list); + assertEquals(1, list.size()); + assertEquals(startTimeRecord3, list.get(0).getStartupTimestamps().get(0).longValue()); + } + private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) { try { Field field = clazz.getDeclaredField(fieldName); diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java index fa7dcc8ebbc7..81d753b8e079 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java @@ -35,6 +35,7 @@ import static com.android.server.wm.AppCompatConfiguration.DEFAULT_LETTERBOX_ASP import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import android.compat.testing.PlatformCompatChangeRule; import android.content.pm.ActivityInfo; @@ -413,7 +414,7 @@ public class DesktopAppCompatAspectRatioPolicyTests extends WindowTestsBase { void setDesiredAspectRatio(float aspectRatio) { doReturn(aspectRatio).when(getDesktopAppCompatAspectRatioPolicy()) - .getDesiredAspectRatio(any()); + .getDesiredAspectRatio(any(), anyBoolean()); } DesktopAppCompatAspectRatioPolicy getDesktopAppCompatAspectRatioPolicy() { @@ -422,7 +423,7 @@ public class DesktopAppCompatAspectRatioPolicyTests extends WindowTestsBase { float calculateAspectRatio() { return getDesktopAppCompatAspectRatioPolicy().calculateAspectRatio( - getTopActivity().getTask()); + getTopActivity().getTask(), /* hasOrientationMismatch */ true); } ActivityRecord getTopActivity() { diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java index 00b617e91bfd..f587d6e8c346 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java @@ -52,6 +52,7 @@ import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier. import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -437,7 +438,7 @@ public class DesktopModeLaunchParamsModifierTests extends spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy()); doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController - .getDesktopAspectRatioPolicy()).calculateAspectRatio(any()); + .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean()); final int desiredWidth = (int) ((LANDSCAPE_DISPLAY_BOUNDS.height() / LETTERBOX_ASPECT_RATIO) + 0.5f); @@ -933,7 +934,7 @@ public class DesktopModeLaunchParamsModifierTests extends spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy()); doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController - .getDesktopAspectRatioPolicy()).calculateAspectRatio(any()); + .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean()); final int desiredHeight = (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE); @@ -1060,7 +1061,7 @@ public class DesktopModeLaunchParamsModifierTests extends spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy()); doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController - .getDesktopAspectRatioPolicy()).calculateAspectRatio(any()); + .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean()); final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2); @@ -1115,7 +1116,7 @@ public class DesktopModeLaunchParamsModifierTests extends spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy()); doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController - .getDesktopAspectRatioPolicy()).calculateAspectRatio(any()); + .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean()); final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2); @@ -1569,7 +1570,7 @@ public class DesktopModeLaunchParamsModifierTests extends activity.mAppCompatController.getDesktopAspectRatioPolicy(); spyOn(desktopAppCompatAspectRatioPolicy); doReturn(aspectRatio).when(desktopAppCompatAspectRatioPolicy) - .getDesiredAspectRatio(any()); + .getDesiredAspectRatio(any(), anyBoolean()); } private void applyUserMinAspectRatioOverride(ActivityRecord activity, int overrideCode, @@ -1579,7 +1580,7 @@ public class DesktopModeLaunchParamsModifierTests extends activity.mAppCompatController.getDesktopAspectRatioPolicy(); spyOn(desktopAppCompatAspectRatioPolicy); doReturn(1f).when(desktopAppCompatAspectRatioPolicy) - .getDesiredAspectRatio(any()); + .getDesiredAspectRatio(any(), anyBoolean()); // Enable user aspect ratio settings final AppCompatConfiguration appCompatConfiguration = |