diff options
40 files changed, 1347 insertions, 295 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 1c6e40e25a92..963307b110cf 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -2085,8 +2085,12 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.v(TAG, debugPrefix + " ready=" + jobReady); } - if (!jobReady) { - return job.getPendingJobReasons(); + final JobRestriction restriction = checkIfRestricted(job); + if (DEBUG) { + Slog.v(TAG, debugPrefix + " restriction=" + restriction); + } + if (!jobReady || restriction != null) { + return job.getPendingJobReasons(restriction); } final boolean userStarted = areUsersStartedLocked(job); @@ -2106,18 +2110,6 @@ public class JobSchedulerService extends com.android.server.SystemService return new int[] { JobScheduler.PENDING_JOB_REASON_APP }; } - final JobRestriction restriction = checkIfRestricted(job); - if (DEBUG) { - Slog.v(TAG, debugPrefix + " restriction=" + restriction); - } - if (restriction != null) { - // Currently this will return _DEVICE_STATE because of thermal reasons. - // TODO (b/372031023): does it make sense to move this along with the - // pendingJobReasons() call above and also get the pending reasons from - // all of the restriction controllers? - return new int[] { restriction.getPendingReason() }; - } - // The following can be a little more expensive, so we are doing it later, // but still before checking with the package manager! final boolean jobPending = mPendingJobQueue.contains(job); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index b0784f1c69fd..a3eaefd5f057 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -66,6 +66,7 @@ import com.android.server.job.JobSchedulerService; import com.android.server.job.JobServerProtoEnums; import com.android.server.job.JobStatusDumpProto; import com.android.server.job.JobStatusShortInfoProto; +import com.android.server.job.restrictions.JobRestriction; import dalvik.annotation.optimization.NeverCompile; @@ -2179,11 +2180,20 @@ public final class JobStatus { * This will return all potential reasons why the job is pending. */ @NonNull - public int[] getPendingJobReasons() { + public int[] getPendingJobReasons(@Nullable JobRestriction restriction) { final int unsatisfiedConstraints = ~satisfiedConstraints & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS); final ArrayList<Integer> reasons = constraintsToPendingJobReasons(unsatisfiedConstraints); + if (restriction != null) { + // Currently only ThermalStatusRestriction extends the JobRestriction class and + // returns PENDING_JOB_REASON_DEVICE_STATE if the job is restricted because of thermal. + @JobScheduler.PendingJobReason final int reason = restriction.getPendingReason(); + if (!reasons.contains(reason)) { + reasons.addLast(reason); + } + } + if (reasons.isEmpty()) { if (getEffectiveStandbyBucket() == NEVER_INDEX) { Slog.wtf(TAG, "App in NEVER bucket querying pending job reason"); diff --git a/core/api/current.txt b/core/api/current.txt index 9bb71ef8ae3c..96968f56545e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -21098,6 +21098,7 @@ package android.inputmethodservice { method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface(); method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); method public boolean onGenericMotionEvent(android.view.MotionEvent); + method @FlaggedApi("android.view.inputmethod.verify_key_event") public boolean onShouldVerifyKeyEvent(@NonNull android.view.KeyEvent); method public boolean onTrackballEvent(android.view.MotionEvent); } @@ -21115,6 +21116,7 @@ package android.inputmethodservice { method public void dispatchTrackballEvent(int, android.view.MotionEvent, android.view.inputmethod.InputMethodSession.EventCallback); method public boolean isEnabled(); method public boolean isRevoked(); + method @FlaggedApi("android.view.inputmethod.verify_key_event") public boolean onShouldVerifyKeyEvent(@NonNull android.view.KeyEvent); method public void revokeSelf(); method public void setEnabled(boolean); } diff --git a/core/java/Android.bp b/core/java/Android.bp index bc38294279a8..bdd7a375e2c4 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -236,6 +236,7 @@ aidl_interface { "android/os/GpuHeadroomParamsInternal.aidl", "android/os/IHintManager.aidl", "android/os/IHintSession.aidl", + "android/os/SessionCreationConfig.aidl", ], unstable: true, backend: { diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java index 4bc5bd2427ea..26308f69cfbe 100644 --- a/core/java/android/inputmethodservice/AbstractInputMethodService.java +++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java @@ -16,6 +16,9 @@ package android.inputmethodservice; +import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT; + +import android.annotation.FlaggedApi; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; @@ -193,6 +196,12 @@ public abstract class AbstractInputMethodService extends WindowProviderService } } + @FlaggedApi(FLAG_VERIFY_KEY_EVENT) + @Override + public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) { + return AbstractInputMethodService.this.onShouldVerifyKeyEvent(event); + } + /** * Take care of dispatching incoming trackball events to the appropriate * callbacks on the service, and tell the client when this is done. @@ -308,6 +317,14 @@ public abstract class AbstractInputMethodService extends WindowProviderService return false; } + /** + * @see InputMethodService#onShouldVerifyKeyEvent(KeyEvent) + */ + @FlaggedApi(FLAG_VERIFY_KEY_EVENT) + public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) { + return false; + } + /** @hide */ @Override public final int getWindowType() { diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index 62b131af74fe..9b37533f5b02 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -16,12 +16,16 @@ package android.inputmethodservice; +import static android.view.inputmethod.Flags.verifyKeyEvent; + import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.Rect; +import android.hardware.input.InputManager; import android.os.Bundle; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; import android.view.InputChannel; @@ -41,6 +45,8 @@ import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import java.util.Objects; + class IInputMethodSessionWrapper extends IInputMethodSession.Stub implements HandlerCaller.Callback { private static final String TAG = "InputMethodWrapper"; @@ -56,6 +62,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub private static final int DO_REMOVE_IME_SURFACE = 130; private static final int DO_FINISH_INPUT = 140; private static final int DO_INVALIDATE_INPUT = 150; + private final Context mContext; @UnsupportedAppUsage @@ -66,6 +73,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub public IInputMethodSessionWrapper(Context context, InputMethodSession inputMethodSession, InputChannel channel) { + mContext = context; mCaller = new HandlerCaller(context, null, this, true /*asyncHandler*/); mInputMethodSession = inputMethodSession; @@ -233,6 +241,8 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub } private final class ImeInputEventReceiver extends InputEventReceiver implements InputMethodSession.EventCallback { + // Time after which a KeyEvent is invalid + private static final long KEY_EVENT_ALLOW_PERIOD_MS = 100L; private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>(); public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) { @@ -247,10 +257,23 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub return; } + if (event instanceof KeyEvent keyEvent && needsVerification(keyEvent)) { + // any KeyEvent with modifiers (e.g. Ctrl/Alt/Fn) must be verified that + // they originated from system. + InputManager im = mContext.getSystemService(InputManager.class); + Objects.requireNonNull(im); + final long age = SystemClock.uptimeMillis() - keyEvent.getEventTime(); + if (age >= KEY_EVENT_ALLOW_PERIOD_MS && im.verifyInputEvent(keyEvent) == null) { + Log.w(TAG, "Unverified or Invalid KeyEvent injected into IME. Dropping " + + keyEvent); + finishInputEvent(event, false /* handled */); + return; + } + } + final int seq = event.getSequenceNumber(); mPendingEvents.put(seq, event); - if (event instanceof KeyEvent) { - KeyEvent keyEvent = (KeyEvent)event; + if (event instanceof KeyEvent keyEvent) { mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this); } else { MotionEvent motionEvent = (MotionEvent)event; @@ -271,5 +294,21 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub finishInputEvent(event, handled); } } + + private boolean hasKeyModifiers(KeyEvent event) { + if (event.hasNoModifiers()) { + return false; + } + return event.hasModifiers(KeyEvent.META_CTRL_ON) + || event.hasModifiers(KeyEvent.META_ALT_ON) + || event.hasModifiers(KeyEvent.KEYCODE_FUNCTION); + } + + private boolean needsVerification(KeyEvent event) { + //TODO(b/331730488): Handle a11y events as well. + return verifyKeyEvent() + && (hasKeyModifiers(event) + || mInputMethodSession.onShouldVerifyKeyEvent(event)); + } } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 977c5bd927cf..4bde8e2b44ee 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -56,6 +56,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED; import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING; import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API; +import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static android.view.inputmethod.Flags.predictiveBackIme; @@ -3777,6 +3778,23 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Received by the IME before dispatch to {@link #onKeyDown(int, KeyEvent)} to let the system + * know if the {@link KeyEvent} needs to be verified that it originated from the system. + * {@link KeyEvent}s may originate from outside of the system and any sensitive keys should be + * marked for verification. One example of this could be using key shortcuts for switching to + * another IME. + * + * @param keyEvent the event that may need verification. + * @return {@code true} if {@link KeyEvent} should have its HMAC verified before dispatch, + * {@code false} otherwise. + */ + @FlaggedApi(FLAG_VERIFY_KEY_EVENT) + @Override + public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent keyEvent) { + return false; + } + + /** * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent) * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle * the event). diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl index 33120556339f..a043da9b7a36 100644 --- a/core/java/android/os/IHintManager.aidl +++ b/core/java/android/os/IHintManager.aidl @@ -20,6 +20,7 @@ package android.os; import android.os.CpuHeadroomParamsInternal; import android.os.GpuHeadroomParamsInternal; import android.os.IHintSession; +import android.os.SessionCreationConfig; import android.hardware.power.ChannelConfig; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; @@ -34,8 +35,8 @@ interface IHintManager { * Throws UnsupportedOperationException if ADPF is not supported, and IllegalStateException * if creation is supported but fails. */ - IHintSession createHintSessionWithConfig(in IBinder token, in int[] threadIds, - in long durationNanos, in SessionTag tag, out SessionConfig config); + IHintSession createHintSessionWithConfig(in IBinder token, in SessionTag tag, + in SessionCreationConfig creationConfig, out SessionConfig config); /** * Get preferred rate limit in nanoseconds. @@ -56,4 +57,9 @@ interface IHintManager { long getCpuHeadroomMinIntervalMillis(); float getGpuHeadroom(in GpuHeadroomParamsInternal params); long getGpuHeadroomMinIntervalMillis(); + + /** + * Get Maximum number of graphics pipeline threads allowed per-app. + */ + int getMaxGraphicsPipelineThreadsCount(); } diff --git a/core/java/android/os/SessionCreationConfig.aidl b/core/java/android/os/SessionCreationConfig.aidl new file mode 100644 index 000000000000..cdc0ef461e0c --- /dev/null +++ b/core/java/android/os/SessionCreationConfig.aidl @@ -0,0 +1,39 @@ +/* + * + * Copyright 2024, 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 android.os; + +import android.hardware.power.SessionTag; +import android.hardware.power.SessionMode; + +/** {@hide} */ +parcelable SessionCreationConfig { + /** + * List of tids to be included in the hint session. + */ + int[] tids; + + /** + * The initial target work duration of this hint session in nanoseconds. + */ + long targetWorkDurationNanos; + + /** + * List of the modes to be enabled upon session creation. + */ + SessionMode[] modesToEnable; +} diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 2ef8764a5221..5ac53f1bf39d 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -12,6 +12,15 @@ flag { } flag { + name: "adpf_graphics_pipeline" + is_exported: true + namespace: "game" + description: "Guards use of SessionCreationConfig and Graphics Pipeline mode" + is_fixed_read_only: true + bug: "367803904" +} + +flag { name: "adpf_hwui_gpu" namespace: "game" description: "Guards use of the FMQ channel for ADPF" diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java index b7b8f0b1b5d8..e257f3409beb 100644 --- a/core/java/android/text/style/TtsSpan.java +++ b/core/java/android/text/style/TtsSpan.java @@ -107,11 +107,13 @@ public class TtsSpan implements ParcelableSpan { /** * The text associated with this span is a time, consisting of a number of - * hours and minutes, specified with {@link #ARG_HOURS} and - * {@link #ARG_MINUTES}. + * hours, minutes, and seconds specified with {@link #ARG_HOURS}, {@link #ARG_MINUTES}, and + * {@link #ARG_SECONDS}. * Also accepts the arguments {@link #ARG_GENDER}, * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and - * {@link #ARG_CASE}. + * {@link #ARG_CASE}. This is different from {@link #TYPE_DURATION}. This should be used to + * convey a particular moment in time, such as a clock time, while {@link #TYPE_DURATION} should + * be used to convey an interval of time. */ public static final String TYPE_TIME = "android.type.time"; @@ -309,16 +311,18 @@ public class TtsSpan implements ParcelableSpan { public static final String ARG_UNIT = "android.arg.unit"; /** - * Argument used to specify the hours of a time. The hours should be - * provided as an integer in the range from 0 up to and including 24. - * Can be used with {@link #TYPE_TIME}. + * Argument used to specify the hours of a time or duration. The hours should be + * provided as an integer in the range from 0 up to and including 24 for + * {@link #TYPE_TIME}. + * Can be used with {@link #TYPE_TIME} or {@link #TYPE_DURATION}. */ public static final String ARG_HOURS = "android.arg.hours"; /** - * Argument used to specify the minutes of a time. The minutes should be - * provided as an integer in the range from 0 up to and including 59. - * Can be used with {@link #TYPE_TIME}. + * Argument used to specify the minutes of a time or duration. The minutes should be + * provided as an integer in the range from 0 up to and including 59 for + * {@link #TYPE_TIME}. + * Can be used with {@link #TYPE_TIME} or {@link #TYPE_DURATION}. */ public static final String ARG_MINUTES = "android.arg.minutes"; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 19d3dc4df04e..5b4b5300cef4 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -110,7 +110,6 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_B import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW; import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; -import static android.view.accessibility.Flags.fixMergedContentChangeEventV2; import static android.view.accessibility.Flags.forceInvertColor; import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle; import static android.view.flags.Flags.addSchandleToVriSurface; @@ -12290,31 +12289,20 @@ public final class ViewRootImpl implements ViewParent, } if (mSource != null) { - if (fixMergedContentChangeEventV2()) { - View newSource = getCommonPredecessor(mSource, source); - if (newSource != null) { - newSource = newSource.getSelfOrParentImportantForA11y(); - } - if (newSource == null) { - // If there is no common predecessor, then mSource points to - // a removed view, hence in this case always prefer the source. - newSource = source; - } - - mChangeTypes |= changeType; - if (mSource != newSource) { - mChangeTypes |= AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; - mSource = newSource; - } - } else { + View newSource = getCommonPredecessor(mSource, source); + if (newSource != null) { + newSource = newSource.getSelfOrParentImportantForA11y(); + } + if (newSource == null) { // If there is no common predecessor, then mSource points to // a removed view, hence in this case always prefer the source. - View predecessor = getCommonPredecessor(mSource, source); - if (predecessor != null) { - predecessor = predecessor.getSelfOrParentImportantForA11y(); - } - mSource = (predecessor != null) ? predecessor : source; - mChangeTypes |= changeType; + newSource = source; + } + + mChangeTypes |= changeType; + if (mSource != newSource) { + mChangeTypes |= AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; + mSource = newSource; } final int performingAction = mAccessibilityManager.getPerformingAction(); diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 8a006fa5b509..e60fc3ae6b47 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -99,16 +99,6 @@ flag { flag { namespace: "accessibility" - name: "fix_merged_content_change_event_v2" - description: "Fixes event type and source of content change event merged in ViewRootImpl" - bug: "277305460" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - namespace: "accessibility" name: "flash_notification_system_api" is_exported: true description: "Makes flash notification APIs as system APIs for calling from mainline module" diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java index 4f48cb684e8c..1806a8369d01 100644 --- a/core/java/android/view/inputmethod/InputMethodSession.java +++ b/core/java/android/view/inputmethod/InputMethodSession.java @@ -16,6 +16,7 @@ package android.view.inputmethod; +import android.annotation.NonNull; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; import android.os.Bundle; @@ -125,6 +126,23 @@ public interface InputMethodSession { public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback); /** + * Received by the IME before dispatch to {@link InputMethodService#onKeyDown(int, KeyEvent)} + * to let the system know if the {@link KeyEvent} needs to be verified that it originated from + * the system. {@link KeyEvent}s may originate from outside of the system and any sensitive keys + * should be marked for verification. One example of this could be using key shortcuts for + * switching to another IME. + * + * @param event the event that may need verification. + * @return {@code true} if {@link KeyEvent} should have its HMAC verified before dispatch, + * {@code false} otherwise. + * + * @hide + */ + default boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) { + return false; + } + + /** * This method is called when there is a track ball event. * * <p> diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index deaf95797127..73abc472be6d 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -184,3 +184,11 @@ flag { bug: "350047836" is_fixed_read_only: true } + +flag { + name: "verify_key_event" + namespace: "input_method" + description: "Verify KeyEvents in IME" + bug: "331730488" + is_fixed_read_only: true +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt index 1acde73e68dc..4723eb273988 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt @@ -119,7 +119,8 @@ class DesktopImmersiveController( ) } - fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo) { + /** Starts a transition to move an immersive task out of immersive. */ + fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo, reason: ExitReason) { if (inProgress) { logV( "Cannot start exit because transition(s) already in progress: %s", @@ -131,7 +132,7 @@ class DesktopImmersiveController( val wct = WindowContainerTransaction().apply { setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) } - logV("Moving task ${taskInfo.taskId} out of immersive mode") + logV("Moving task %d out of immersive mode, reason: %s", taskInfo.taskId, reason) val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) state = TransitionState( transition = transition, @@ -151,10 +152,11 @@ class DesktopImmersiveController( fun exitImmersiveIfApplicable( transition: IBinder, wct: WindowContainerTransaction, - displayId: Int + displayId: Int, + reason: ExitReason, ) { if (!Flags.enableFullyImmersiveInDesktop()) return - val result = exitImmersiveIfApplicable(wct, displayId) + val result = exitImmersiveIfApplicable(wct, displayId, excludeTaskId = null, reason) result.asExit()?.runOnTransitionStart?.invoke(transition) } @@ -170,6 +172,7 @@ class DesktopImmersiveController( wct: WindowContainerTransaction, displayId: Int, excludeTaskId: Int? = null, + reason: ExitReason, ): ExitResult { if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) @@ -179,7 +182,10 @@ class DesktopImmersiveController( } val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return ExitResult.NoExit - logV("Appending immersive exit for task: $immersiveTask in display: $displayId") + logV( + "Appending immersive exit for task: %d in display: %d for reason: %s", + immersiveTask, displayId, reason + ) wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) return ExitResult.Exit( exitingTask = immersiveTask, @@ -198,14 +204,15 @@ class DesktopImmersiveController( */ fun exitImmersiveIfApplicable( wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, + reason: ExitReason, ): ExitResult { if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { // A full immersive task is being minimized, make sure the immersive state is broken // (i.e. resize back to max bounds). wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) - logV("Appending immersive exit for task: ${taskInfo.taskId}") + logV("Appending immersive exit for task: %d for reason: %s", taskInfo.taskId, reason) return ExitResult.Exit( exitingTask = taskInfo.taskId, runOnTransitionStart = { transition -> @@ -550,6 +557,15 @@ class DesktopImmersiveController( ENTER, EXIT } + /** The reason for moving the task out of desktop immersive mode. */ + enum class ExitReason { + APP_NOT_IMMERSIVE, // The app stopped requesting immersive treatment. + USER_INTERACTION, // Explicit user intent request, e.g. a button click. + TASK_LAUNCH, // A task launched/moved on top of the immersive task. + MINIMIZED, // The immersive task was minimized. + CLOSED, // The immersive task was closed. + } + private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index a0bdd9fad510..eb930b0d9e00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -70,7 +70,6 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags -import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut import com.android.wm.shell.Flags.enableFlexibleSplit import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -133,6 +132,8 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeT import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION +import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState +import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING /** Handles moving tasks in and out of desktop */ class DesktopTasksController( @@ -209,7 +210,9 @@ class DesktopTasksController( val draggingTaskId get() = dragToDesktopTransitionHandler.draggingTaskId - private var recentsAnimationRunning = false + @RecentsTransitionState + private var recentsTransitionState = TRANSITION_STATE_NOT_RUNNING + private lateinit var splitScreenController: SplitScreenController lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter // Launch cookie used to identify a drag and drop transition to fullscreen after it has begun. @@ -238,10 +241,15 @@ class DesktopTasksController( dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener recentsTransitionHandler.addTransitionStateListener( object : RecentsTransitionStateListener { - override fun onAnimationStateChanged(running: Boolean) { - logV("Recents animation state changed running=%b", running) - recentsAnimationRunning = running - desktopTilingDecorViewModel.onOverviewAnimationStateChange(running) + override fun onTransitionStateChanged(@RecentsTransitionState state: Int) { + logV( + "Recents transition state changed: %s", + RecentsTransitionStateListener.stateToString(state) + ) + recentsTransitionState = state + desktopTilingDecorViewModel.onOverviewAnimationStateChange( + RecentsTransitionStateListener.isAnimating(state) + ) } } ) @@ -381,6 +389,7 @@ class DesktopTasksController( wct = wct, displayId = DEFAULT_DISPLAY, excludeTaskId = taskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) wct.startTask( taskId, @@ -413,6 +422,7 @@ class DesktopTasksController( wct = wct, displayId = task.displayId, excludeTaskId = task.taskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) // Bring other apps to front first val taskIdToMinimize = @@ -460,7 +470,11 @@ class DesktopTasksController( bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId) addMoveToDesktopChanges(wct, taskInfo) val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( - wct, taskInfo.displayId) + wct = wct, + displayId = taskInfo.displayId, + excludeTaskId = null, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH + ) val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt() @@ -508,8 +522,11 @@ class DesktopTasksController( taskId ) ) - return desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo).asExit() - ?.runOnTransitionStart + return desktopImmersiveController.exitImmersiveIfApplicable( + wct = wct, + taskInfo = taskInfo, + reason = DesktopImmersiveController.ExitReason.CLOSED + ).asExit()?.runOnTransitionStart } fun minimizeTask(taskInfo: RunningTaskInfo) { @@ -518,7 +535,11 @@ class DesktopTasksController( val wct = WindowContainerTransaction() performDesktopExitCleanupIfNeeded(taskId, wct) // Notify immersive handler as it might need to exit immersive state. - val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo) + val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( + wct = wct, + taskInfo = taskInfo, + reason = DesktopImmersiveController.ExitReason.MINIMIZED + ) wct.reorder(taskInfo.token, false) val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct) @@ -675,6 +696,7 @@ class DesktopTasksController( wct = wct, displayId = displayId, excludeTaskId = launchingTaskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) if (remoteTransition == null) { val t = desktopMixedTransitionHandler.startLaunchTransition( @@ -766,7 +788,10 @@ class DesktopTasksController( /** Moves a task in/out of full immersive state within the desktop. */ fun toggleDesktopTaskFullImmersiveState(taskInfo: RunningTaskInfo) { if (taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { - exitDesktopTaskFromFullImmersive(taskInfo) + exitDesktopTaskFromFullImmersive( + taskInfo, + DesktopImmersiveController.ExitReason.USER_INTERACTION, + ) } else { moveDesktopTaskToFullImmersive(taskInfo) } @@ -777,9 +802,12 @@ class DesktopTasksController( desktopImmersiveController.moveTaskToImmersive(taskInfo) } - private fun exitDesktopTaskFromFullImmersive(taskInfo: RunningTaskInfo) { + private fun exitDesktopTaskFromFullImmersive( + taskInfo: RunningTaskInfo, + reason: DesktopImmersiveController.ExitReason, + ) { check(taskInfo.isFreeform) { "Task must already be in freeform" } - desktopImmersiveController.moveTaskToNonImmersive(taskInfo) + desktopImmersiveController.moveTaskToNonImmersive(taskInfo, reason) } /** @@ -1292,6 +1320,8 @@ class DesktopTasksController( // Check if we should skip handling this transition var reason = "" val triggerTask = request.triggerTask + val recentsAnimationRunning = + RecentsTransitionStateListener.isAnimating(recentsTransitionState) var shouldHandleMidRecentsFreeformLaunch = recentsAnimationRunning && isFreeformRelaunch(triggerTask, request) val isDragAndDropFullscreenTransition = taskContainsDragAndDropCookie(triggerTask) @@ -1479,6 +1509,7 @@ class DesktopTasksController( wct = wct, displayId = callingTask.displayId, excludeTaskId = requestedTaskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) val transition = transitions.startTransition(TRANSIT_OPEN, wct, null) taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } @@ -1628,7 +1659,12 @@ class DesktopTasksController( } // Desktop Mode is showing and we're launching a new Task: // 1) Exit immersive if needed. - desktopImmersiveController.exitImmersiveIfApplicable(transition, wct, task.displayId) + desktopImmersiveController.exitImmersiveIfApplicable( + transition = transition, + wct = wct, + displayId = task.displayId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, + ) // 2) minimize a Task if needed. val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId) addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) @@ -1665,7 +1701,10 @@ class DesktopTasksController( taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) desktopImmersiveController.exitImmersiveIfApplicable( - transition, wct, task.displayId + transition, + wct, + task.displayId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH ) } } else if (taskRepository.isActiveTask(task.taskId)) { @@ -2282,9 +2321,13 @@ class DesktopTasksController( if (!Flags.enableFullyImmersiveInDesktop()) return val inImmersive = taskRepository.isTaskInFullImmersiveState(taskInfo.taskId) val requestingImmersive = taskInfo.requestingImmersive - if (inImmersive && !requestingImmersive) { + if (inImmersive && !requestingImmersive + && !RecentsTransitionStateListener.isRunning(recentsTransitionState)) { // Exit immersive if the app is no longer requesting it. - exitDesktopTaskFromFullImmersive(taskInfo) + exitDesktopTaskFromFullImmersive( + taskInfo, + DesktopImmersiveController.ExitReason.APP_NOT_IMMERSIVE + ) } } 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 6da4f510ab77..d917f937b16c 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 @@ -665,8 +665,10 @@ public class RecentTasksController implements TaskStackListenerCallback, } mTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() { @Override - public void onAnimationStateChanged(boolean running) { - executor.execute(() -> listener.accept(running)); + public void onTransitionStateChanged(@RecentsTransitionState int state) { + executor.execute(() -> { + listener.accept(RecentsTransitionStateListener.isAnimating(state)); + }); } }); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 1c58dbbf71fd..032dac9ff3a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -32,6 +32,9 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED; import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION; import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION; @@ -166,13 +169,19 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // only care about latest one. mAnimApp = appThread; + for (int i = 0; i < mStateListeners.size(); i++) { + mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_REQUESTED); + } // TODO(b/366021931): Formalize this later - final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition"); + final boolean isSyntheticRequest = options.getBoolean( + "is_synthetic_recents_transition", /* defaultValue= */ false); + final IBinder transition; if (isSyntheticRequest) { - return startSyntheticRecentsTransition(listener); + transition = startSyntheticRecentsTransition(listener); } else { - return startRealRecentsTransition(intent, fillIn, options, listener); + transition = startRealRecentsTransition(intent, fillIn, options, listener); } + return transition; } /** @@ -542,7 +551,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, mPendingFinishTransition = null; mControllers.remove(this); for (int i = 0; i < mStateListeners.size(); i++) { - mStateListeners.get(i).onAnimationStateChanged(false); + mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_NOT_RUNNING); } } @@ -578,7 +587,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, new RemoteAnimationTarget[0], new Rect(0, 0, 0, 0), new Rect(), new Bundle()); for (int i = 0; i < mStateListeners.size(); i++) { - mStateListeners.get(i).onAnimationStateChanged(true); + mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING); } } catch (RemoteException e) { Slog.e(TAG, "Error starting recents animation", e); @@ -809,7 +818,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]), new Rect(0, 0, 0, 0), new Rect(), b); for (int i = 0; i < mStateListeners.size(); i++) { - mStateListeners.get(i).onAnimationStateChanged(true); + mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING); } } catch (RemoteException e) { Slog.e(TAG, "Error starting recents animation", e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java index 95874c8193c9..ea7cfd374f71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java @@ -16,12 +16,47 @@ package com.android.wm.shell.recents; -import android.os.IBinder; +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** The listener for the events from {@link RecentsTransitionHandler}. */ public interface RecentsTransitionStateListener { - /** Notifies whether the recents animation is running. */ - default void onAnimationStateChanged(boolean running) { + @IntDef(prefix = { "TRANSITION_STATE_" }, value = { + TRANSITION_STATE_NOT_RUNNING, + TRANSITION_STATE_REQUESTED, + TRANSITION_STATE_ANIMATING, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RecentsTransitionState {} + + int TRANSITION_STATE_NOT_RUNNING = 1; + int TRANSITION_STATE_REQUESTED = 2; + int TRANSITION_STATE_ANIMATING = 3; + + /** Notifies whether the recents transition state changes. */ + default void onTransitionStateChanged(@RecentsTransitionState int state) { + } + + /** Returns whether the recents transition is running. */ + static boolean isRunning(@RecentsTransitionState int state) { + return state >= TRANSITION_STATE_REQUESTED; + } + + /** Returns whether the recents transition is animating. */ + static boolean isAnimating(@RecentsTransitionState int state) { + return state >= TRANSITION_STATE_ANIMATING; + } + + /** Returns a string representation of the given state. */ + static String stateToString(@RecentsTransitionState int state) { + return switch (state) { + case TRANSITION_STATE_NOT_RUNNING -> "TRANSITION_STATE_NOT_RUNNING"; + case TRANSITION_STATE_REQUESTED -> "TRANSITION_STATE_REQUESTED"; + case TRANSITION_STATE_ANIMATING -> "TRANSITION_STATE_ANIMATING"; + default -> "UNKNOWN"; + }; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt index 7b71e41874c7..b8c91519422c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt @@ -23,6 +23,7 @@ import android.graphics.Rect import android.view.WindowManager import android.window.TaskSnapshot import androidx.compose.ui.graphics.toArgb +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer import com.android.wm.shell.shared.split.SplitScreenConstants import com.android.wm.shell.splitscreen.SplitScreenController @@ -101,6 +102,7 @@ class DesktopHandleManageWindowsMenu( flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, view = menuView.rootView, + ignoreCutouts = DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context), ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index 456f2c062c59..8a2b39438a71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -45,6 +45,7 @@ import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting import androidx.compose.ui.graphics.toArgb import androidx.core.view.isGone +import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.apptoweb.isBrowserApp import com.android.wm.shell.shared.split.SplitScreenConstants @@ -218,7 +219,8 @@ class HandleMenu( WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, view = handleMenuView.rootView, - forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 } + forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 }, + ignoreCutouts = Flags.showAppHandleLargeScreens() ) } else { parentDecor.addWindow( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt index 8b6aaaf619e0..4a01e8e3d455 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt @@ -25,6 +25,7 @@ import android.view.SurfaceControl import android.view.View import android.view.WindowInsets import android.view.WindowManager +import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS import com.android.wm.shell.windowdecor.WindowManagerWrapper /** @@ -40,6 +41,7 @@ class AdditionalSystemViewContainer( height: Int, flags: Int, @WindowInsets.Type.InsetsType forciblyShownTypes: Int = 0, + ignoreCutouts: Boolean = false, override val view: View ) : AdditionalViewContainer() { val lp: WindowManager.LayoutParams = WindowManager.LayoutParams( @@ -52,6 +54,10 @@ class AdditionalSystemViewContainer( gravity = Gravity.LEFT or Gravity.TOP setTrustedOverlay() this.forciblyShownTypes = forciblyShownTypes + if (ignoreCutouts) { + fitInsetsTypes = 0 + layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + } } constructor( @@ -63,7 +69,8 @@ class AdditionalSystemViewContainer( width: Int, height: Int, flags: Int, - @LayoutRes layoutId: Int + @LayoutRes layoutId: Int, + ignoreCutouts: Boolean = false ) : this( windowManagerWrapper = windowManagerWrapper, taskId = taskId, @@ -72,7 +79,8 @@ class AdditionalSystemViewContainer( width = width, height = height, flags = flags, - view = LayoutInflater.from(context).inflate(layoutId, null /* parent */) + view = LayoutInflater.from(context).inflate(layoutId, null /* parent */), + ignoreCutouts = ignoreCutouts ) constructor( @@ -83,7 +91,8 @@ class AdditionalSystemViewContainer( y: Int, width: Int, height: Int, - flags: Int + flags: Int, + ignoreCutouts: Boolean = false ) : this( windowManagerWrapper = windowManagerWrapper, taskId = taskId, @@ -92,7 +101,8 @@ class AdditionalSystemViewContainer( width = width, height = height, flags = flags, - view = View(context) + view = View(context), + ignoreCutouts = ignoreCutouts ) init { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index 5f25f42039ef..3f65d9318692 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -38,6 +38,7 @@ import android.window.DesktopModeFlags import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat import com.android.internal.policy.SystemBarUtils +import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.windowdecor.WindowManagerWrapper @@ -143,7 +144,8 @@ internal class AppHandleViewHolder( if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper, taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + ignoreCutouts = Flags.showAppHandleLargeScreens() ) val view = statusBarInputLayer?.view ?: error("Unable to find statusBarInputLayer View") val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer " + diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java index f935ac76bbeb..9c31b46a80e5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java @@ -45,6 +45,7 @@ public final class TestRunningTaskInfoBuilder { private Intent mBaseIntent = new Intent(); private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD; private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED; + private @WindowConfiguration.ActivityType int mTopActivityType = ACTIVITY_TYPE_STANDARD; private int mDisplayId = Display.DEFAULT_DISPLAY; private ActivityManager.TaskDescription.Builder mTaskDescriptionBuilder = null; private final Point mPositionInParent = new Point(); @@ -102,6 +103,12 @@ public final class TestRunningTaskInfoBuilder { return this; } + public TestRunningTaskInfoBuilder setTopActivityType( + @WindowConfiguration.ActivityType int activityType) { + mTopActivityType = activityType; + return this; + } + public TestRunningTaskInfoBuilder setWindowingMode( @WindowConfiguration.WindowingMode int windowingMode) { mWindowingMode = windowingMode; @@ -154,6 +161,7 @@ public final class TestRunningTaskInfoBuilder { info.configuration.windowConfiguration.setBounds(mBounds); info.configuration.windowConfiguration.setActivityType(mActivityType); info.configuration.windowConfiguration.setWindowingMode(mWindowingMode); + info.topActivityType = mTopActivityType; info.token = mToken; info.isResizeable = true; info.supportsMultiWindow = true; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index a4f4d05d2079..4666276c2fae 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -42,6 +42,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitReason.USER_INTERACTION import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -168,7 +169,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.onTransitionReady( transition = mockBinder, info = createTransitionInfo( @@ -195,7 +196,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { ) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600)) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.onTransitionReady( transition = mockBinder, info = createTransitionInfo( @@ -252,8 +253,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) .thenReturn(mockBinder) - controller.moveTaskToNonImmersive(task) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) verify(mockTransitions, times(1)) .startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)) @@ -272,7 +273,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY @@ -293,7 +294,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY @@ -314,7 +315,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(wct.hasBoundsChange(task.token)).isTrue() } @@ -332,7 +333,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(wct.hasBoundsChange(task.token)).isFalse() } @@ -353,7 +354,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.exitImmersiveIfApplicable( wct = wct, displayId = DEFAULT_DISPLAY, - excludeTaskId = task.taskId + excludeTaskId = task.taskId, + reason = USER_INTERACTION, ).asExit()?.runOnTransitionStart?.invoke(transition) assertThat(controller.pendingExternalExitTransitions.any { exit -> @@ -374,7 +376,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) assertThat(wct.hasBoundsChange(task.token)).isTrue() } @@ -391,7 +393,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - controller.exitImmersiveIfApplicable(wct, task) + controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) assertThat(wct.hasBoundsChange(task.token)).isFalse() } @@ -409,7 +411,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(wct, task) + controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) .asExit()?.runOnTransitionStart?.invoke(transition) assertThat(controller.pendingExternalExitTransitions.any { exit -> @@ -430,7 +432,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - val result = controller.exitImmersiveIfApplicable(wct, task) + val result = controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) assertThat(result).isEqualTo(DesktopImmersiveController.ExitResult.NoExit) } @@ -447,7 +449,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - val result = controller.exitImmersiveIfApplicable(wct, task.displayId) + val result = controller.exitImmersiveIfApplicable( + wct, task.displayId, excludeTaskId = null, USER_INTERACTION) assertThat(result).isEqualTo(DesktopImmersiveController.ExitResult.NoExit) } @@ -464,7 +467,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, @@ -495,7 +498,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, @@ -530,7 +533,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, @@ -560,7 +563,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600)) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, @@ -587,7 +590,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) assertThat( wct.hasBoundsChange(task.token, calculateMaximizeBounds(mockDisplayLayout, task)) @@ -611,7 +614,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { val preImmersiveBounds = Rect(100, 100, 500, 500) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, preImmersiveBounds) - controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) assertThat( wct.hasBoundsChange(task.token, preImmersiveBounds) @@ -634,7 +637,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) assertThat( wct.hasBoundsChange(task.token, calculateInitialBounds(mockDisplayLayout, task)) @@ -652,10 +655,10 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - controller.exitImmersiveIfApplicable(wct, task) + controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) .asExit()?.runOnTransitionStart?.invoke(Binder()) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) verify(mockTransitions, never()).startTransition(any(), any(), any()) } @@ -674,7 +677,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(controller.isImmersiveChange(transition, change)).isTrue() } @@ -692,7 +695,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.animateResizeChange( change = TransitionInfo.Change(task.token, SurfaceControl()).apply { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 5df395754c7a..490e6b9a226b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -113,6 +113,8 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING +import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN import com.android.wm.shell.shared.split.SplitScreenConstants @@ -291,10 +293,10 @@ class DesktopTasksControllerTest : ShellTestCase() { tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), any<RunningTaskInfo>())) + .exitImmersiveIfApplicable(any(), any<RunningTaskInfo>(), any())) .thenReturn(ExitResult.NoExit) whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull())) + .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any())) .thenReturn(ExitResult.NoExit) controller = createController() @@ -1793,7 +1795,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.minimizeTask(task) - verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task)) + verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any()) } @Test @@ -1803,7 +1805,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnTransit = RunOnStartTransitionCallback() whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task))) + whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any())) .thenReturn( ExitResult.Exit( exitingTask = task.taskId, @@ -2192,7 +2194,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskVisible(freeformTask) // Mark recents animation running - recentsTransitionStateListener.onAnimationStateChanged(true) + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) // Open a fullscreen task, check that it does not result in a WCT with changes to it val fullscreenTask = createFullscreenTask() @@ -2206,7 +2208,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskVisible(freeformTask) // Mark recents animation running - recentsTransitionStateListener.onAnimationStateChanged(true) + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA. val result = controller.handleRequest(Binder(), createTransition(freeformTask)) @@ -3189,7 +3191,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val wctCaptor = argumentCaptor<WindowContainerTransaction>() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull())) + .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any())) .thenReturn(ExitResult.NoExit) whenever(desktopMixedTransitionHandler .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull())) @@ -3212,7 +3214,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStart = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull())) + .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any())) .thenReturn(ExitResult.Exit(immersiveTask.taskId, runOnStart)) whenever(desktopMixedTransitionHandler .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull())) @@ -3315,7 +3317,8 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.startTransition(eq(TRANSIT_OPEN), any(), anyOrNull())) .thenReturn(transition) whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId))) + .exitImmersiveIfApplicable( + any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = immersiveTask.taskId, @@ -3325,7 +3328,7 @@ class DesktopTasksControllerTest : ShellTestCase() { runOpenInstance(immersiveTask, freeformTask.taskId) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId)) + .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3740,7 +3743,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.toggleDesktopTaskFullImmersiveState(task) - verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(task) + verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any()) } @Test @@ -3752,7 +3755,7 @@ class DesktopTasksControllerTest : ShellTestCase() { task.requestedVisibleTypes = WindowInsets.Type.statusBars() controller.onTaskInfoChanged(task) - verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(task) + verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any()) } @Test @@ -3764,7 +3767,20 @@ class DesktopTasksControllerTest : ShellTestCase() { task.requestedVisibleTypes = WindowInsets.Type.statusBars() controller.onTaskInfoChanged(task) - verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(task) + verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_inRecentsTransition_noExit() { + val task = setUpFreeformTask(DEFAULT_DISPLAY) + taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true) + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_REQUESTED) + + task.requestedVisibleTypes = WindowInsets.Type.statusBars() + controller.onTaskInfoChanged(task) + + verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any()) } @Test @@ -3774,7 +3790,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(wct, task.displayId, task.taskId)) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = 5, @@ -3785,7 +3801,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(wct, task.displayId, task.taskId) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3796,7 +3812,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(wct, task.displayId, task.taskId)) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = 5, @@ -3807,7 +3823,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(wct, task.displayId, task.taskId) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3817,7 +3833,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId))) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = 5, @@ -3830,7 +3846,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveTaskToFront(task.taskId, remoteTransition = null) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3840,7 +3856,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId))) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = 5, @@ -3853,7 +3869,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveTaskToFront(task.taskId, remoteTransition = null) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3867,7 +3883,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.handleRequest(binder, createTransition(task)) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId)) + .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any()) } @Test @@ -3879,7 +3895,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.handleRequest(binder, createTransition(task)) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId)) + .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any()) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java index 6087763b4978..f0f5fe159069 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java @@ -16,7 +16,16 @@ package com.android.wm.shell.recents; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED; +import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION; + +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; @@ -27,6 +36,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.KeyguardManager; @@ -38,7 +48,10 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.platform.test.flag.junit.SetFlagsRule; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -47,6 +60,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.os.IResultReceiver; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; @@ -56,7 +70,9 @@ import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.HomeTransitionObserver; +import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.StubTransaction; import org.junit.After; import org.junit.Before; @@ -93,6 +109,8 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { private IRecentTasksListener mRecentTasksListener; @Mock private TaskStackTransitionObserver mTaskStackTransitionObserver; + @Mock + private Transitions mTransitions; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -129,10 +147,9 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController), mMainExecutor); - final Transitions transitions = mock(Transitions.class); - doReturn(mMainExecutor).when(transitions).getMainExecutor(); + doReturn(mMainExecutor).when(mTransitions).getMainExecutor(); mRecentsTransitionHandler = new RecentsTransitionHandler(mShellInit, mShellTaskOrganizer, - transitions, mRecentTasksController, mock(HomeTransitionObserver.class)); + mTransitions, mRecentTasksController, mock(HomeTransitionObserver.class)); mShellInit.init(); } @@ -146,12 +163,8 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { public void testStartSyntheticRecentsTransition_callsOnAnimationStartAndFinishCallback() throws Exception { final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class); final IResultReceiver finishCallback = mock(IResultReceiver.class); - doReturn(new Binder()).when(runner).asBinder(); - Bundle options = new Bundle(); - options.putBoolean("is_synthetic_recents_transition", true); - IBinder transition = mRecentsTransitionHandler.startRecentsTransition( - mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class), - runner); + + final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner); verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any()); // Finish and verify no transition remains and that the provided finish callback is called @@ -165,12 +178,8 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { @Test public void testStartSyntheticRecentsTransition_callsOnAnimationCancel() throws Exception { final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class); - doReturn(new Binder()).when(runner).asBinder(); - Bundle options = new Bundle(); - options.putBoolean("is_synthetic_recents_transition", true); - IBinder transition = mRecentsTransitionHandler.startRecentsTransition( - mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class), - runner); + + final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner); verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any()); mRecentsTransitionHandler.findController(transition).cancel("test"); @@ -178,4 +187,137 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { verify(runner).onAnimationCanceled(any(), any()); assertNull(mRecentsTransitionHandler.findController(transition)); } + + @Test + public void testStartTransition_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + startRecentsTransition(/* synthetic= */ false); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_REQUESTED); + } + + @Test + public void testStartAnimation_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ false); + mRecentsTransitionHandler.startAnimation( + transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(), + mock(Transitions.TransitionFinishCallback.class)); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_ANIMATING); + } + + @Test + public void testFinishTransition_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ false); + mRecentsTransitionHandler.startAnimation( + transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(), + mock(Transitions.TransitionFinishCallback.class)); + mRecentsTransitionHandler.findController(transition).finish(true /* toHome */, + false /* sendUserLeaveHint */, mock(IResultReceiver.class)); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_NOT_RUNNING); + } + + @Test + public void testCancelTransition_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ false); + mRecentsTransitionHandler.findController(transition).cancel("test"); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_NOT_RUNNING); + } + + @Test + public void testStartAnimation_synthetic_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + startRecentsTransition(/* synthetic= */ true); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_ANIMATING); + } + + @Test + public void testFinishTransition_synthetic_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ true); + mRecentsTransitionHandler.findController(transition).finish(true /* toHome */, + false /* sendUserLeaveHint */, mock(IResultReceiver.class)); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_NOT_RUNNING); + } + + @Test + public void testCancelTransition_synthetic_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ true); + mRecentsTransitionHandler.findController(transition).cancel("test"); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_NOT_RUNNING); + } + + private IBinder startRecentsTransition(boolean synthetic) { + return startRecentsTransition(synthetic, mock(IRecentsAnimationRunner.class)); + } + + private IBinder startRecentsTransition(boolean synthetic, + @NonNull IRecentsAnimationRunner runner) { + doReturn(new Binder()).when(runner).asBinder(); + final Bundle options = new Bundle(); + options.putBoolean("is_synthetic_recents_transition", synthetic); + final IBinder transition = new Binder(); + when(mTransitions.startTransition(anyInt(), any(), any())).thenReturn(transition); + return mRecentsTransitionHandler.startRecentsTransition( + mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class), + runner); + } + + private TransitionInfo createTransitionInfo() { + final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder() + .setTopActivityType(ACTIVITY_TYPE_HOME) + .build(); + final TransitionInfo.Change homeChange = new TransitionInfo.Change( + task.token, new SurfaceControl()); + homeChange.setMode(TRANSIT_TO_FRONT); + homeChange.setTaskInfo(task); + return new TransitionInfoBuilder(TRANSIT_START_RECENTS_TRANSITION) + .addChange(homeChange) + .build(); + } + + private static class TestTransitionStateListener implements RecentsTransitionStateListener { + @RecentsTransitionState + private int mState = TRANSITION_STATE_NOT_RUNNING; + + @Override + public void onTransitionStateChanged(int state) { + mState = state; + } + + @RecentsTransitionState + int getState() { + return mState; + } + } } diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 6b41ddde5cc8..9da7becba332 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -355,12 +355,14 @@ LIBANDROID { APerformanceHint_getManager; # introduced=Tiramisu APerformanceHint_createSession; # introduced=Tiramisu APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu + APerformanceHint_getMaxGraphicsPipelineThreadsCount; # introduced=36 APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu APerformanceHint_closeSession; # introduced=Tiramisu APerformanceHint_setThreads; # introduced=UpsideDownCake APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream + APerformanceHint_createSessionUsingConfig; # introduced=36 APerformanceHint_notifyWorkloadIncrease; # introduced=36 APerformanceHint_notifyWorkloadReset; # introduced=36 APerformanceHint_borrowSessionFromJava; # introduced=36 @@ -370,6 +372,12 @@ LIBANDROID { AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream + ASessionCreationConfig_create; # introduced=36 + ASessionCreationConfig_release; # introduced=36 + ASessionCreationConfig_setTids; # introduced=36 + ASessionCreationConfig_setTargetWorkDurationNanos; # introduced=36 + ASessionCreationConfig_setPreferPowerEfficiency; # introduced=36 + ASessionCreationConfig_setGraphicsPipeline; # introduced=36 local: *; }; @@ -379,8 +387,10 @@ LIBANDROID_PLATFORM { AThermal_setIThermalServiceForTesting; APerformanceHint_setIHintManagerForTesting; APerformanceHint_sendHint; + APerformanceHint_setUseGraphicsPipelineForTesting; APerformanceHint_getThreadIds; APerformanceHint_createSessionInternal; + APerformanceHint_createSessionUsingConfigInternal; APerformanceHint_setUseFMQForTesting; APerformanceHint_getRateLimiterPropertiesForTesting; APerformanceHint_setUseNewLoadHintBehaviorForTesting; diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index bc1945e37072..c67e93c99e52 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -26,6 +26,7 @@ #include <aidl/android/hardware/power/WorkDurationFixedV1.h> #include <aidl/android/os/IHintManager.h> #include <aidl/android/os/IHintSession.h> +#include <aidl/android/os/SessionCreationConfig.h> #include <android-base/stringprintf.h> #include <android-base/thread_annotations.h> #include <android/binder_manager.h> @@ -65,6 +66,13 @@ struct APerformanceHintSession; constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count(); struct AWorkDuration : public hal::WorkDuration {}; +struct ASessionCreationConfig : public SessionCreationConfig {}; + +bool kForceGraphicsPipeline = false; + +bool useGraphicsPipeline() { + return android::os::adpf_graphics_pipeline() || kForceGraphicsPipeline; +} // A pair of values that determine the behavior of the // load hint rate limiter, to only allow "X hints every Y seconds" @@ -142,7 +150,11 @@ public: bool isJava = false); APerformanceHintSession* getSessionFromJava(JNIEnv* _Nonnull env, jobject _Nonnull sessionObj); + APerformanceHintSession* createSessionUsingConfig(ASessionCreationConfig* sessionCreationConfig, + hal::SessionTag tag = hal::SessionTag::APP, + bool isJava = false); int64_t getPreferredRateNanos() const; + int32_t getMaxGraphicsPipelineThreadsCount(); FMQWrapper& getFMQWrapper(); bool canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) REQUIRES(sHintMutex); void initJava(JNIEnv* _Nonnull env); @@ -163,6 +175,7 @@ private: std::shared_ptr<IHintManager> mHintManager; ndk::SpAIBinder mToken; const int64_t mPreferredRateNanos; + std::optional<int32_t> mMaxGraphicsPipelineThreadsCount; FMQWrapper mFMQWrapper; double mHintBudget = kMaxLoadHintsPerInterval; int64_t mLastBudgetReplenish = 0; @@ -220,8 +233,10 @@ private: std::vector<int32_t> mLastThreadIDs GUARDED_BY(sHintMutex); std::optional<hal::SessionConfig> mSessionConfig GUARDED_BY(sHintMutex); // Tracing helpers - void traceThreads(std::vector<int32_t>& tids) REQUIRES(sHintMutex); + void traceThreads(const std::vector<int32_t>& tids) REQUIRES(sHintMutex); void tracePowerEfficient(bool powerEfficient); + void traceGraphicsPipeline(bool graphicsPipeline); + void traceModes(const std::vector<hal::SessionMode>& modesToEnable); void traceActualDuration(int64_t actualDuration); void traceBatchSize(size_t batchSize); void traceTargetDuration(int64_t targetDuration); @@ -311,27 +326,45 @@ bool APerformanceHintManager::canSendLoadHints(std::vector<hal::SessionHint>& hi APerformanceHintSession* APerformanceHintManager::createSession( const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, hal::SessionTag tag, bool isJava) { - std::vector<int32_t> tids(threadIds, threadIds + size); - std::shared_ptr<IHintSession> session; ndk::ScopedAStatus ret; hal::SessionConfig sessionConfig{.id = -1}; - ret = mHintManager->createHintSessionWithConfig(mToken, tids, initialTargetWorkDurationNanos, - tag, &sessionConfig, &session); + + SessionCreationConfig creationConfig{ + .tids = std::vector<int32_t>(threadIds, threadIds + size), + .targetWorkDurationNanos = initialTargetWorkDurationNanos, + }; + + return APerformanceHintManager::createSessionUsingConfig(static_cast<ASessionCreationConfig*>( + &creationConfig), + tag, isJava); +} + +APerformanceHintSession* APerformanceHintManager::createSessionUsingConfig( + ASessionCreationConfig* sessionCreationConfig, hal::SessionTag tag, bool isJava) { + std::shared_ptr<IHintSession> session; + hal::SessionConfig sessionConfig{.id = -1}; + ndk::ScopedAStatus ret; + + ret = mHintManager->createHintSessionWithConfig(mToken, tag, + *static_cast<SessionCreationConfig*>( + sessionCreationConfig), + &sessionConfig, &session); if (!ret.isOk() || !session) { ALOGE("%s: PerformanceHint cannot create session. %s", __FUNCTION__, ret.getMessage()); return nullptr; } auto out = new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos, - initialTargetWorkDurationNanos, isJava, + sessionCreationConfig->targetWorkDurationNanos, isJava, sessionConfig.id == -1 ? std::nullopt : std::make_optional<hal::SessionConfig>( std::move(sessionConfig))); std::scoped_lock lock(sHintMutex); - out->traceThreads(tids); - out->traceTargetDuration(initialTargetWorkDurationNanos); - out->tracePowerEfficient(false); + out->traceThreads(sessionCreationConfig->tids); + out->traceTargetDuration(sessionCreationConfig->targetWorkDurationNanos); + out->traceModes(sessionCreationConfig->modesToEnable); + return out; } @@ -351,6 +384,23 @@ int64_t APerformanceHintManager::getPreferredRateNanos() const { return mPreferredRateNanos; } +int32_t APerformanceHintManager::getMaxGraphicsPipelineThreadsCount() { + if (!mMaxGraphicsPipelineThreadsCount.has_value()) { + int32_t threadsCount = -1; + ndk::ScopedAStatus ret = mHintManager->getMaxGraphicsPipelineThreadsCount(&threadsCount); + if (!ret.isOk()) { + ALOGE("%s: PerformanceHint cannot get max graphics pipeline threads count. %s", + __FUNCTION__, ret.getMessage()); + return -1; + } + if (threadsCount <= 0) { + threadsCount = -1; + } + mMaxGraphicsPipelineThreadsCount.emplace(threadsCount); + } + return mMaxGraphicsPipelineThreadsCount.value(); +} + FMQWrapper& APerformanceHintManager::getFMQWrapper() { return mFMQWrapper; } @@ -787,7 +837,7 @@ bool FMQWrapper::setMode(std::optional<hal::SessionConfig>& config, hal::Session // ===================================== Tracing helpers -void APerformanceHintSession::traceThreads(std::vector<int32_t>& tids) { +void APerformanceHintSession::traceThreads(const std::vector<int32_t>& tids) { std::set<int32_t> tidSet{tids.begin(), tids.end()}; // Disable old TID tracing @@ -813,6 +863,28 @@ void APerformanceHintSession::tracePowerEfficient(bool powerEfficient) { ATrace_setCounter((mSessionName + " power efficiency mode").c_str(), powerEfficient); } +void APerformanceHintSession::traceGraphicsPipeline(bool graphicsPipeline) { + ATrace_setCounter((mSessionName + " graphics pipeline mode").c_str(), graphicsPipeline); +} + +void APerformanceHintSession::traceModes(const std::vector<hal::SessionMode>& modesToEnable) { + // Iterate through all modes to trace, set to enable for all modes in modesToEnable, + // and set to disable for those are not. + for (hal::SessionMode mode : + {hal::SessionMode::POWER_EFFICIENCY, hal::SessionMode::GRAPHICS_PIPELINE}) { + bool isEnabled = + find(modesToEnable.begin(), modesToEnable.end(), mode) != modesToEnable.end(); + switch (mode) { + case hal::SessionMode::POWER_EFFICIENCY: + tracePowerEfficient(isEnabled); + break; + case hal::SessionMode::GRAPHICS_PIPELINE: + traceGraphicsPipeline(isEnabled); + break; + } + } +} + void APerformanceHintSession::traceActualDuration(int64_t actualDuration) { ATrace_setCounter((mSessionName + " actual duration").c_str(), actualDuration); } @@ -855,6 +927,22 @@ APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* return manager->createSession(threadIds, size, initialTargetWorkDurationNanos); } +APerformanceHintSession* APerformanceHint_createSessionUsingConfig( + APerformanceHintManager* manager, ASessionCreationConfig* sessionCreationConfig) { + VALIDATE_PTR(manager); + VALIDATE_PTR(sessionCreationConfig); + return manager->createSessionUsingConfig(sessionCreationConfig); +} + +APerformanceHintSession* APerformanceHint_createSessionUsingConfigInternal( + APerformanceHintManager* manager, ASessionCreationConfig* sessionCreationConfig, + SessionTag tag) { + VALIDATE_PTR(manager); + VALIDATE_PTR(sessionCreationConfig); + return manager->createSessionUsingConfig(sessionCreationConfig, + static_cast<hal::SessionTag>(tag)); +} + APerformanceHintSession* APerformanceHint_createSessionInternal( APerformanceHintManager* manager, const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, SessionTag tag) { @@ -885,6 +973,11 @@ int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* ma return manager->getPreferredRateNanos(); } +int APerformanceHint_getMaxGraphicsPipelineThreadsCount(APerformanceHintManager* manager) { + VALIDATE_PTR(manager); + return manager->getMaxGraphicsPipelineThreadsCount(); +} + int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session, int64_t targetDurationNanos) { VALIDATE_PTR(session) @@ -1018,6 +1111,81 @@ void APerformanceHint_setUseFMQForTesting(bool enabled) { gForceFMQEnabled = enabled; } +ASessionCreationConfig* ASessionCreationConfig_create() { + return new ASessionCreationConfig(); +} + +void ASessionCreationConfig_release(ASessionCreationConfig* config) { + VALIDATE_PTR(config) + + delete config; +} + +int ASessionCreationConfig_setTids(ASessionCreationConfig* config, const pid_t* tids, size_t size) { + VALIDATE_PTR(config) + VALIDATE_PTR(tids) + + if (!useGraphicsPipeline()) { + return ENOTSUP; + } + + if (size <= 0) { + LOG_ALWAYS_FATAL_IF(size <= 0, + "%s: Invalid value. Thread id list size should be greater than zero.", + __FUNCTION__); + return EINVAL; + } + config->tids = std::vector<int32_t>(tids, tids + size); + return 0; +} + +int ASessionCreationConfig_setTargetWorkDurationNanos(ASessionCreationConfig* config, + int64_t targetWorkDurationNanos) { + VALIDATE_PTR(config) + VALIDATE_INT(targetWorkDurationNanos, >= 0) + + if (!useGraphicsPipeline()) { + return ENOTSUP; + } + + config->targetWorkDurationNanos = targetWorkDurationNanos; + return 0; +} + +int ASessionCreationConfig_setPreferPowerEfficiency(ASessionCreationConfig* config, bool enabled) { + VALIDATE_PTR(config) + + if (!useGraphicsPipeline()) { + return ENOTSUP; + } + + if (enabled) { + config->modesToEnable.push_back(hal::SessionMode::POWER_EFFICIENCY); + } else { + std::erase(config->modesToEnable, hal::SessionMode::POWER_EFFICIENCY); + } + return 0; +} + +int ASessionCreationConfig_setGraphicsPipeline(ASessionCreationConfig* config, bool enabled) { + VALIDATE_PTR(config) + + if (!useGraphicsPipeline()) { + return ENOTSUP; + } + + if (enabled) { + config->modesToEnable.push_back(hal::SessionMode::GRAPHICS_PIPELINE); + } else { + std::erase(config->modesToEnable, hal::SessionMode::GRAPHICS_PIPELINE); + } + return 0; +} + +void APerformanceHint_setUseGraphicsPipelineForTesting(bool enabled) { + kForceGraphicsPipeline = enabled; +} + void APerformanceHint_getRateLimiterPropertiesForTesting(int32_t* maxLoadHintsPerInterval, int64_t* loadHintInterval) { *maxLoadHintsPerInterval = kMaxLoadHintsPerInterval; diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index f707a0e9b0b2..c5fb80840b84 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -18,9 +18,11 @@ #include <aidl/android/hardware/power/ChannelConfig.h> #include <aidl/android/hardware/power/SessionConfig.h> +#include <aidl/android/hardware/power/SessionMode.h> #include <aidl/android/hardware/power/SessionTag.h> #include <aidl/android/hardware/power/WorkDuration.h> #include <aidl/android/os/IHintManager.h> +#include <aidl/android/os/SessionCreationConfig.h> #include <android/binder_manager.h> #include <android/binder_status.h> #include <android/performance_hint.h> @@ -36,6 +38,7 @@ using namespace std::chrono_literals; namespace hal = aidl::android::hardware::power; using aidl::android::os::IHintManager; using aidl::android::os::IHintSession; +using aidl::android::os::SessionCreationConfig; using ndk::ScopedAStatus; using ndk::SpAIBinder; using HalChannelMessageContents = hal::ChannelMessage::ChannelMessageContents; @@ -49,11 +52,13 @@ using namespace testing; class MockIHintManager : public IHintManager { public: MOCK_METHOD(ScopedAStatus, createHintSessionWithConfig, - (const SpAIBinder& token, const ::std::vector<int32_t>& tids, int64_t durationNanos, - hal::SessionTag tag, hal::SessionConfig* config, + (const SpAIBinder& token, hal::SessionTag tag, + const SessionCreationConfig& creationConfig, hal::SessionConfig* config, std::shared_ptr<IHintSession>* _aidl_return), (override)); MOCK_METHOD(ScopedAStatus, getHintSessionPreferredRate, (int64_t * _aidl_return), (override)); + MOCK_METHOD(ScopedAStatus, getMaxGraphicsPipelineThreadsCount, (int32_t* _aidl_return), + (override)); MOCK_METHOD(ScopedAStatus, setHintSessionThreads, (const std::shared_ptr<IHintSession>& hintSession, const ::std::vector<int32_t>& tids), @@ -105,6 +110,7 @@ public: APerformanceHint_getRateLimiterPropertiesForTesting(&mMaxLoadHintsPerInterval, &mLoadHintInterval); APerformanceHint_setIHintManagerForTesting(&mMockIHintManager); + APerformanceHint_setUseGraphicsPipelineForTesting(true); APerformanceHint_setUseNewLoadHintBehaviorForTesting(true); } @@ -118,21 +124,22 @@ public: APerformanceHint_setUseFMQForTesting(mUsingFMQ); ON_CALL(*mMockIHintManager, getHintSessionPreferredRate(_)) .WillByDefault(DoAll(SetArgPointee<0>(123L), [] { return ScopedAStatus::ok(); })); + ON_CALL(*mMockIHintManager, getMaxGraphicsPipelineThreadsCount(_)) + .WillByDefault(DoAll(SetArgPointee<0>(5), [] { return ScopedAStatus::ok(); })); return APerformanceHint_getManager(); } APerformanceHintSession* createSession(APerformanceHintManager* manager, int64_t targetDuration = 56789L, bool isHwui = false) { mMockSession = ndk::SharedRefBase::make<NiceMock<MockIHintSession>>(); - int64_t sessionId = 123; + const int64_t sessionId = 123; std::vector<int32_t> tids; tids.push_back(1); tids.push_back(2); - ON_CALL(*mMockIHintManager, - createHintSessionWithConfig(_, Eq(tids), Eq(targetDuration), _, _, _)) - .WillByDefault(DoAll(SetArgPointee<4>(hal::SessionConfig({.id = sessionId})), - SetArgPointee<5>(std::shared_ptr<IHintSession>(mMockSession)), + ON_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)) + .WillByDefault(DoAll(SetArgPointee<3>(hal::SessionConfig({.id = sessionId})), + SetArgPointee<4>(std::shared_ptr<IHintSession>(mMockSession)), [] { return ScopedAStatus::ok(); })); ON_CALL(*mMockIHintManager, setHintSessionThreads(_, _)).WillByDefault([] { @@ -157,6 +164,43 @@ public: return APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); } + APerformanceHintSession* createSessionUsingConfig(APerformanceHintManager* manager, + SessionCreationConfig config, + bool isHwui = false) { + mMockSession = ndk::SharedRefBase::make<NiceMock<MockIHintSession>>(); + const int64_t sessionId = 123; + + ON_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)) + .WillByDefault(DoAll(SetArgPointee<3>(hal::SessionConfig({.id = sessionId})), + SetArgPointee<4>(std::shared_ptr<IHintSession>(mMockSession)), + [] { return ScopedAStatus::ok(); })); + + ON_CALL(*mMockIHintManager, setHintSessionThreads(_, _)).WillByDefault([] { + return ScopedAStatus::ok(); + }); + ON_CALL(*mMockSession, sendHint(_)).WillByDefault([] { return ScopedAStatus::ok(); }); + ON_CALL(*mMockSession, setMode(_, true)).WillByDefault([] { return ScopedAStatus::ok(); }); + ON_CALL(*mMockSession, close()).WillByDefault([] { return ScopedAStatus::ok(); }); + ON_CALL(*mMockSession, updateTargetWorkDuration(_)).WillByDefault([] { + return ScopedAStatus::ok(); + }); + ON_CALL(*mMockSession, reportActualWorkDuration(_, _)).WillByDefault([] { + return ScopedAStatus::ok(); + }); + ON_CALL(*mMockSession, reportActualWorkDuration2(_)).WillByDefault([] { + return ScopedAStatus::ok(); + }); + + if (isHwui) { + return APerformanceHint_createSessionUsingConfigInternal( + manager, reinterpret_cast<ASessionCreationConfig*>(&config), SessionTag::HWUI); + } + + return APerformanceHint_createSessionUsingConfig(manager, + reinterpret_cast<ASessionCreationConfig*>( + &config)); + } + void setFMQEnabled(bool enabled) { mUsingFMQ = enabled; if (enabled) { @@ -272,16 +316,26 @@ TEST_F(PerformanceHintTest, TestSession) { } TEST_F(PerformanceHintTest, TestUpdatedSessionCreation) { - EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _, _)).Times(1); + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)).Times(1); APerformanceHintManager* manager = createManager(); APerformanceHintSession* session = createSession(manager); ASSERT_TRUE(session); APerformanceHint_closeSession(session); } +TEST_F(PerformanceHintTest, TestSessionCreationUsingConfig) { + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)).Times(1); + SessionCreationConfig config{.tids = std::vector<int32_t>(1, 2), + .targetWorkDurationNanos = 5678, + .modesToEnable = std::vector<hal::SessionMode>(0)}; + APerformanceHintManager* manager = createManager(); + APerformanceHintSession* session = createSessionUsingConfig(manager, config); + ASSERT_TRUE(session); + APerformanceHint_closeSession(session); +} + TEST_F(PerformanceHintTest, TestHwuiSessionCreation) { - EXPECT_CALL(*mMockIHintManager, - createHintSessionWithConfig(_, _, _, hal::SessionTag::HWUI, _, _)) + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, hal::SessionTag::HWUI, _, _, _)) .Times(1); APerformanceHintManager* manager = createManager(); APerformanceHintSession* session = createSession(manager, 56789L, true); @@ -447,3 +501,16 @@ TEST_F(PerformanceHintTest, TestReportActualUsingFMQ) { reinterpret_cast<AWorkDuration*>(&duration)); expectToReadFromFmq<HalChannelMessageContents::Tag::workDuration>(durationExpected); } + +TEST_F(PerformanceHintTest, TestASessionCreationConfig) { + ASessionCreationConfig* config = ASessionCreationConfig_create(); + ASSERT_NE(config, nullptr); + + const int32_t testTids[2] = {1, 2}; + const size_t size = 2; + EXPECT_EQ(ASessionCreationConfig_setTids(config, testTids, size), 0); + EXPECT_EQ(ASessionCreationConfig_setTargetWorkDurationNanos(config, 20), 0); + EXPECT_EQ(ASessionCreationConfig_setPreferPowerEfficiency(config, true), 0); + EXPECT_EQ(ASessionCreationConfig_setGraphicsPipeline(config, true), 0); + ASessionCreationConfig_release(config); +} diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java index f3820e5935d4..8c028bc92841 100644 --- a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java +++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java @@ -210,16 +210,16 @@ final class AppLaunchShortcutManager { /** * Handle the shortcut to {@link IShortcutService} - * @param keyCode The key code of the event. - * @param metaState The meta key modifier state. - * @return True if invoked the shortcut, otherwise false. + * @return true if invoked the shortcut, otherwise false. */ - private boolean handleShortcutService(int keyCode, int metaState) { - final long shortcutCodeMeta = metaState & SHORTCUT_CODE_META_MASK; + public boolean handleShortcutService(KeyEvent event) { + // TODO(b/358569822): Ideally shortcut service custom shortcuts should be either + // migrated to bookmarks or customizable shortcut APIs. + final long shortcutCodeMeta = event.getMetaState() & SHORTCUT_CODE_META_MASK; if (shortcutCodeMeta == 0) { return false; } - long shortcutCode = keyCode | (shortcutCodeMeta << Integer.SIZE); + long shortcutCode = event.getKeyCode() | (shortcutCodeMeta << Integer.SIZE); IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode); if (shortcutService != null) { try { @@ -292,7 +292,6 @@ final class AppLaunchShortcutManager { return InterceptKeyResult.DO_NOTHING; } - final int metaState = event.getModifiers(); final int keyCode = event.getKeyCode(); if (keyCode == KeyEvent.KEYCODE_SEARCH) { if (event.getAction() == KeyEvent.ACTION_DOWN) { @@ -313,15 +312,7 @@ final class AppLaunchShortcutManager { } // Intercept shortcuts defined in bookmarks or through application launch keycodes - AppLaunchData appLaunchData = interceptShortcut(event); - - // TODO(b/358569822): Ideally shortcut service custom shortcuts should be either - // migrated to bookmarks or customizable shortcut APIs. - if (appLaunchData == null && handleShortcutService(keyCode, metaState)) { - return InterceptKeyResult.CONSUME_KEY; - } - - return new InterceptKeyResult(/* consumed =*/ false, appLaunchData); + return new InterceptKeyResult(/* consumed =*/ false, interceptShortcut(event)); } /** diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index bb0b19009962..55d2de2b6865 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -793,6 +793,11 @@ final class KeyGestureController { return true; } + // Handle shortcuts through shortcut services + if (mAppLaunchShortcutManager.handleShortcutService(event)) { + return true; + } + // Handle custom shortcuts if (firstDown) { InputGestureData customGesture; diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 7c9d9c57a8b0..76049ca824c2 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -325,7 +325,7 @@ public class ContextHubService extends IContextHubService.Stub { return; } - if (Flags.offloadApi()) { + if (Flags.offloadApi() && Flags.offloadImplementation()) { HubInfoRegistry registry; try { registry = new HubInfoRegistry(mContextHubWrapper); @@ -527,8 +527,8 @@ public class ContextHubService extends IContextHubService.Stub { try { mContextHubWrapper.registerEndpointCallback( new ContextHubHalEndpointCallback(mHubInfoRegistry)); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException while registering IEndpointCallback", e); + } catch (RemoteException | UnsupportedOperationException e) { + Log.e(TAG, "Exception while registering IEndpointCallback", e); } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index e6f784c71ef3..749952e3336f 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -184,6 +184,7 @@ public class PreferencesHelper implements RankingConfig { private static final boolean DEFAULT_SHOW_BADGE = true; private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE = false; + private static final boolean DEFAULT_CAN_HAVE_PROMOTED_NOTIFS = true; static final boolean DEFAULT_BUBBLES_ENABLED = true; @VisibleForTesting @@ -369,8 +370,8 @@ public class PreferencesHelper implements RankingConfig { null, ATT_USER_DEMOTED_INVALID_MSG_APP, false); r.hasSentValidBubble = parser.getAttributeBoolean(null, ATT_SENT_VALID_BUBBLE, false); if (android.app.Flags.uiRichOngoing()) { - r.canHavePromotedNotifs = - parser.getAttributeBoolean(null, ATT_PROMOTE_NOTIFS, false); + r.canHavePromotedNotifs = parser.getAttributeBoolean(null, ATT_PROMOTE_NOTIFS, + DEFAULT_CAN_HAVE_PROMOTED_NOTIFS); } final int innerDepth = parser.getDepth(); @@ -748,7 +749,7 @@ public class PreferencesHelper implements RankingConfig { r.userDemotedMsgApp); out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble); if (android.app.Flags.uiRichOngoing()) { - if (r.canHavePromotedNotifs) { + if (r.canHavePromotedNotifs != DEFAULT_CAN_HAVE_PROMOTED_NOTIFS) { out.attributeBoolean(null, ATT_PROMOTE_NOTIFS, r.canHavePromotedNotifs); } } @@ -2331,7 +2332,8 @@ public class PreferencesHelper implements RankingConfig { pw.print(" fixedImportance="); pw.print(r.fixedImportance); } - if (android.app.Flags.uiRichOngoing() && r.canHavePromotedNotifs) { + if (android.app.Flags.uiRichOngoing() + && r.canHavePromotedNotifs != DEFAULT_CAN_HAVE_PROMOTED_NOTIFS) { pw.print(" promoted="); pw.print(r.canHavePromotedNotifs); } @@ -3184,7 +3186,8 @@ public class PreferencesHelper implements RankingConfig { long creationTime; @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING) - boolean canHavePromotedNotifs = false; + // Until we enable the UI, we should return false. + boolean canHavePromotedNotifs = android.app.Flags.uiRichOngoing(); @UserIdInt int userId; diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 17459df2bc1a..71f67d82dbec 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -52,6 +52,7 @@ import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SessionCreationConfig; import android.os.SystemProperties; import android.text.TextUtils; import android.util.ArrayMap; @@ -70,6 +71,7 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; +import com.android.server.power.hint.HintManagerService.AppHintSession.SessionModes; import com.android.server.utils.Slogf; import java.io.FileDescriptor; @@ -102,6 +104,8 @@ public final class HintManagerService extends SystemService { @VisibleForTesting final long mHintSessionPreferredRate; + @VisibleForTesting static final int MAX_GRAPHICS_PIPELINE_THREADS_COUNT = 5; + // Multi-level map storing all active AppHintSessions. // First level is keyed by the UID of the client process creating the session. // Second level is keyed by an IBinder passed from client process. This is used to observe @@ -129,6 +133,14 @@ public final class HintManagerService extends SystemService { @GuardedBy("mSessionSnapshotMapLock") private ArrayMap<Integer, ArrayMap<Integer, AppHintSessionSnapshot>> mSessionSnapshotMap; + /* + * App UID to Thread mapping. + * Thread is a sub class bookkeeping TID, thread mode (especially graphics pipeline mode) + * This is to bookkeep and track the thread usage. + */ + @GuardedBy("mThreadsUsageObject") + private ArrayMap<Integer, ArraySet<ThreadUsageTracker>> mThreadsUsageMap; + /** Lock to protect mActiveSessions and the UidObserver. */ private final Object mLock = new Object(); @@ -144,6 +156,9 @@ public final class HintManagerService extends SystemService { */ private final Object mSessionSnapshotMapLock = new Object(); + /** Lock to protect mThreadsUsageMap. */ + private final Object mThreadsUsageObject = new Object(); + @GuardedBy("mNonIsolatedTidsLock") private final Map<Integer, Set<Long>> mNonIsolatedTids; @@ -262,6 +277,7 @@ public final class HintManagerService extends SystemService { mActiveSessions = new ArrayMap<>(); mChannelMap = new ArrayMap<>(); mSessionSnapshotMap = new ArrayMap<>(); + mThreadsUsageMap = new ArrayMap<>(); mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper.halInit(); mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); @@ -357,6 +373,36 @@ public final class HintManagerService extends SystemService { } } + private static class ThreadUsageTracker { + /* + * Thread object for tracking thread usage per UID + */ + int mTid; + boolean mIsGraphicsPipeline; + + ThreadUsageTracker(int tid) { + mTid = tid; + mIsGraphicsPipeline = false; + } + + ThreadUsageTracker(int tid, boolean isGraphicsPipeline) { + mTid = tid; + mIsGraphicsPipeline = isGraphicsPipeline; + } + + public int getTid() { + return mTid; + } + + public boolean isGraphicsPipeline() { + return mIsGraphicsPipeline; + } + + public void setGraphicsPipeline(boolean isGraphicsPipeline) { + mIsGraphicsPipeline = isGraphicsPipeline; + } + } + private class AppHintSessionSnapshot { /* * Per-Uid and Per-SessionTag snapshot that tracks metrics including @@ -368,6 +414,7 @@ public final class HintManagerService extends SystemService { int mMaxConcurrentSession; int mMaxThreadCount; int mPowerEfficientSessionCount; + int mGraphicsPipelineSessionCount; final int mTargetDurationNsCountPQSize = 100; PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ; @@ -425,6 +472,7 @@ public final class HintManagerService extends SystemService { mMaxConcurrentSession = 0; mMaxThreadCount = 0; mPowerEfficientSessionCount = 0; + mGraphicsPipelineSessionCount = 0; mTargetDurationNsCountPQ = new PriorityQueue<>(1); } @@ -443,6 +491,10 @@ public final class HintManagerService extends SystemService { mPowerEfficientSessionCount += 1; } + void logGraphicsPipelineSession() { + mGraphicsPipelineSessionCount += 1; + } + void updateThreadCount(int threadCount) { mMaxThreadCount = Math.max(mMaxThreadCount, threadCount); } @@ -473,6 +525,10 @@ public final class HintManagerService extends SystemService { return mPowerEfficientSessionCount; } + int getGraphicsPipelineSessionCount() { + return mGraphicsPipelineSessionCount; + } + long[] targetDurationNsList() { final int listSize = 5; long[] targetDurations = new long[listSize]; @@ -1163,24 +1219,40 @@ public final class HintManagerService extends SystemService { + " doesn't belong to the calling application " + callingUid; } + private boolean contains(final int[] array, final int target) { + for (int element : array) { + if (element == target) { + return true; + } + } + return false; + } + @VisibleForTesting final class BinderService extends IHintManager.Stub { @Override public IHintSession createHintSessionWithConfig(@NonNull IBinder token, - @NonNull int[] tids, long durationNanos, @SessionTag int tag, - SessionConfig config) { + @SessionTag int tag, SessionCreationConfig creationConfig, + SessionConfig config) { if (!isHalSupported()) { throw new UnsupportedOperationException("PowerHAL is not supported!"); } java.util.Objects.requireNonNull(token); - java.util.Objects.requireNonNull(tids); + java.util.Objects.requireNonNull(creationConfig.tids); + + final int[] tids = creationConfig.tids; Preconditions.checkArgument(tids.length != 0, "tids should" + " not be empty."); + final int callingUid = Binder.getCallingUid(); final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final long identity = Binder.clearCallingIdentity(); + final long durationNanos = creationConfig.targetWorkDurationNanos; + + Preconditions.checkArgument(checkGraphicsPipelineValid(creationConfig, callingUid), + "not enough of available graphics pipeline thread."); try { final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray(tids.length) : null; @@ -1274,8 +1346,9 @@ public final class HintManagerService extends SystemService { .computeIfAbsent(tag, k -> new AppHintSessionSnapshot()) .updateUponSessionCreation(tids.length, durationNanos); } + AppHintSession hs = null; synchronized (mLock) { - AppHintSession hs = new AppHintSession(callingUid, callingTgid, tag, tids, + hs = new AppHintSession(callingUid, callingTgid, tag, tids, token, halSessionPtr, durationNanos); ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(callingUid); @@ -1290,9 +1363,29 @@ public final class HintManagerService extends SystemService { } sessionSet.add(hs); mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid); + } + + if (hs != null) { + boolean isGraphicsPipeline = false; + if (creationConfig.modesToEnable != null) { + for (int sessionMode : creationConfig.modesToEnable) { + if (sessionMode == SessionModes.GRAPHICS_PIPELINE.ordinal()) { + isGraphicsPipeline = true; + } + hs.setMode(sessionMode, true); + } + } - return hs; + synchronized (mThreadsUsageObject) { + mThreadsUsageMap.computeIfAbsent(callingUid, k -> new ArraySet<>()); + ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid); + for (int i = 0; i < tids.length; ++i) { + threadsSet.add(new ThreadUsageTracker(tids[i], isGraphicsPipeline)); + } + } } + + return hs; } finally { Binder.restoreCallingIdentity(identity); } @@ -1335,6 +1428,11 @@ public final class HintManagerService extends SystemService { } @Override + public int getMaxGraphicsPipelineThreadsCount() { + return MAX_GRAPHICS_PIPELINE_THREADS_COUNT; + } + + @Override public void setHintSessionThreads(@NonNull IHintSession hintSession, @NonNull int[] tids) { AppHintSession appHintSession = (AppHintSession) hintSession; appHintSession.setThreads(tids); @@ -1470,6 +1568,7 @@ public final class HintManagerService extends SystemService { return; } pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate); + pw.println("MaxGraphicsPipelineThreadsCount: " + MAX_GRAPHICS_PIPELINE_THREADS_COUNT); pw.println("HAL Support: " + isHalSupported()); pw.println("Active Sessions:"); synchronized (mLock) { @@ -1507,6 +1606,45 @@ public final class HintManagerService extends SystemService { } } + private boolean checkGraphicsPipelineValid(SessionCreationConfig creationConfig, int uid) { + if (creationConfig.modesToEnable == null) { + return true; + } + boolean setGraphicsPipeline = false; + for (int modeToEnable : creationConfig.modesToEnable) { + if (modeToEnable == SessionModes.GRAPHICS_PIPELINE.ordinal()) { + setGraphicsPipeline = true; + } + } + if (!setGraphicsPipeline) { + return true; + } + + synchronized (mThreadsUsageObject) { + // count used graphics pipeline threads for the calling UID + // consider the case that new tids are overlapping with in session tids + ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(uid); + if (threadsSet == null) { + return true; + } + + final int newThreadCount = creationConfig.tids.length; + int graphicsPipelineThreadCount = 0; + for (ThreadUsageTracker t : threadsSet) { + // count graphics pipeline threads in use + // and exclude overlapping ones + if (t.isGraphicsPipeline()) { + graphicsPipelineThreadCount++; + if (contains(creationConfig.tids, t.getTid())) { + graphicsPipelineThreadCount--; + } + } + } + return graphicsPipelineThreadCount + newThreadCount + <= MAX_GRAPHICS_PIPELINE_THREADS_COUNT; + } + } + private void logPerformanceHintSessionAtom(int uid, long sessionId, long targetDuration, int[] tids, @SessionTag int sessionTag) { FrameworkStatsLog.write(FrameworkStatsLog.PERFORMANCE_HINT_SESSION_REPORTED, uid, @@ -1537,11 +1675,14 @@ public final class HintManagerService extends SystemService { protected boolean mUpdateAllowedByProcState; protected int[] mNewThreadIds; protected boolean mPowerEfficient; + protected boolean mGraphicsPipeline; protected boolean mHasBeenPowerEfficient; + protected boolean mHasBeenGraphicsPipeline; protected boolean mShouldForcePause; - private enum SessionModes { + enum SessionModes { POWER_EFFICIENCY, + GRAPHICS_PIPELINE, }; protected AppHintSession( @@ -1556,7 +1697,9 @@ public final class HintManagerService extends SystemService { mTargetDurationNanos = durationNanos; mUpdateAllowedByProcState = true; mPowerEfficient = false; + mGraphicsPipeline = false; mHasBeenPowerEfficient = false; + mHasBeenGraphicsPipeline = false; mShouldForcePause = false; final boolean allowed = mUidObserver.isUidForeground(mUid); updateHintAllowedByProcState(allowed); @@ -1677,6 +1820,25 @@ public final class HintManagerService extends SystemService { } sessionSnapshot.updateUponSessionClose(); } + + if (mGraphicsPipeline) { + synchronized (mThreadsUsageObject) { + ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(mUid); + if (threadsSet == null) { + Slogf.w(TAG, "Threads Set is null for uid " + mUid); + return; + } + // remove all tids associated with this session + for (int i = 0; i < threadsSet.size(); ++i) { + if (contains(mThreadIds, threadsSet.valueAt(i).getTid())) { + threadsSet.removeAt(i); + } + } + if (threadsSet.isEmpty()) { + mThreadsUsageMap.remove(mUid); + } + } + } if (powerhintThreadCleanup()) { synchronized (mNonIsolatedTidsLock) { final int[] tids = getTidsInternal(); @@ -1713,6 +1875,33 @@ public final class HintManagerService extends SystemService { throw new IllegalArgumentException("Thread id list can't be empty."); } + + final int callingUid = Binder.getCallingUid(); + if (mGraphicsPipeline) { + synchronized (mThreadsUsageObject) { + // replace original tids with new tids + ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid); + int graphicsPipelineThreadCount = 0; + if (threadsSet != null) { + // We count the graphics pipeline threads that are + // *not* in this session, since those in this session + // will be replaced. Then if the count plus the new tids + // is over max available graphics pipeline threads we raise + // an exception. + for (ThreadUsageTracker t : threadsSet) { + if (t.isGraphicsPipeline() && !contains(mThreadIds, t.getTid())) { + graphicsPipelineThreadCount++; + } + } + if (graphicsPipelineThreadCount + tids.length + > MAX_GRAPHICS_PIPELINE_THREADS_COUNT) { + throw new IllegalArgumentException( + "Not enough available graphics pipeline threads."); + } + } + } + } + synchronized (this) { if (mHalSessionPtr == 0) { return; @@ -1724,7 +1913,6 @@ public final class HintManagerService extends SystemService { return; } if (checkTid) { - final int callingUid = Binder.getCallingUid(); final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray() : null; final long identity = Binder.clearCallingIdentity(); @@ -1770,6 +1958,23 @@ public final class HintManagerService extends SystemService { } } mNativeWrapper.halSetThreads(mHalSessionPtr, tids); + + synchronized (mThreadsUsageObject) { + // replace old tids with new ones + ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid); + if (threadsSet == null) { + mThreadsUsageMap.put(callingUid, new ArraySet<ThreadUsageTracker>()); + threadsSet = mThreadsUsageMap.get(callingUid); + } + for (int i = 0; i < threadsSet.size(); ++i) { + if (contains(mThreadIds, threadsSet.valueAt(i).getTid())) { + threadsSet.removeAt(i); + } + } + for (int tid : tids) { + threadsSet.add(new ThreadUsageTracker(tid, mGraphicsPipeline)); + } + } mThreadIds = tids; mNewThreadIds = null; // if the update is allowed but the session is force paused by tid clean up, then @@ -1831,26 +2036,49 @@ public final class HintManagerService extends SystemService { + " greater than zero."); if (mode == SessionModes.POWER_EFFICIENCY.ordinal()) { mPowerEfficient = enabled; + } else if (mode == SessionModes.GRAPHICS_PIPELINE.ordinal()) { + mGraphicsPipeline = enabled; } mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled); } - if (enabled && (mode == SessionModes.POWER_EFFICIENCY.ordinal())) { - if (!mHasBeenPowerEfficient) { - mHasBeenPowerEfficient = true; - synchronized (mSessionSnapshotMapLock) { - ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = - mSessionSnapshotMap.get(mUid); - if (sessionSnapshots == null) { - Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); - return; + if (enabled) { + if (mode == SessionModes.POWER_EFFICIENCY.ordinal()) { + if (!mHasBeenPowerEfficient) { + mHasBeenPowerEfficient = true; + synchronized (mSessionSnapshotMapLock) { + ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = + mSessionSnapshotMap.get(mUid); + if (sessionSnapshots == null) { + Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); + return; + } + AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); + if (sessionSnapshot == null) { + Slogf.w(TAG, "Session snapshot is null for uid " + mUid + + " and tag " + mTag); + return; + } + sessionSnapshot.logPowerEfficientSession(); } - AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); - if (sessionSnapshot == null) { - Slogf.w(TAG, "Session snapshot is null for uid " + mUid - + " and tag " + mTag); - return; + } + } else if (mode == SessionModes.GRAPHICS_PIPELINE.ordinal()) { + if (!mHasBeenGraphicsPipeline) { + mHasBeenGraphicsPipeline = true; + synchronized (mSessionSnapshotMapLock) { + ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = + mSessionSnapshotMap.get(mUid); + if (sessionSnapshots == null) { + Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); + return; + } + AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); + if (sessionSnapshot == null) { + Slogf.w(TAG, "Session snapshot is null for uid " + mUid + + " and tag " + mTag); + return; + } + sessionSnapshot.logGraphicsPipelineSession(); } - sessionSnapshot.logPowerEfficientSession(); } } } @@ -1877,6 +2105,12 @@ public final class HintManagerService extends SystemService { } } + public boolean isGraphicsPipeline() { + synchronized (this) { + return mGraphicsPipeline; + } + } + public int getUid() { return mUid; } @@ -1964,6 +2198,7 @@ public final class HintManagerService extends SystemService { pw.println(prefix + "SessionAllowedByProcState: " + mUpdateAllowedByProcState); pw.println(prefix + "SessionForcePaused: " + mShouldForcePause); pw.println(prefix + "PowerEfficient: " + (mPowerEfficient ? "true" : "false")); + pw.println(prefix + "GraphicsPipeline: " + (mGraphicsPipeline ? "true" : "false")); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 6292cbfad00b..dde213de1d40 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -19245,21 +19245,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private boolean isAnyResetPasswordTokenActiveForUser(int userId) { + private boolean isAnyResetPasswordTokenActiveForUserLocked(int userId) { return mDevicePolicyEngine .getLocalPoliciesSetByAdmins(PolicyDefinition.RESET_PASSWORD_TOKEN, userId) - .values() + .entrySet() .stream() - .anyMatch((p) -> isResetPasswordTokenActiveForUserLocked(p.getValue(), userId)); + .anyMatch((e) -> { + EnforcingAdmin admin = e.getKey(); + PolicyValue<Long> policyValue = e.getValue(); + return isResetPasswordTokenActiveForUserLocked(policyValue.getValue(), userId) + && isEncryptionAware(admin.getPackageName(), userId); + }); } private boolean isResetPasswordTokenActiveForUserLocked( long passwordTokenHandle, int userHandle) { - if (passwordTokenHandle != 0) { - return mInjector.binderWithCleanCallingIdentity(() -> + return passwordTokenHandle != 0 && mInjector.binderWithCleanCallingIdentity(() -> mLockPatternUtils.isEscrowTokenActive(passwordTokenHandle, userHandle)); - } - return false; } @Override @@ -21108,10 +21110,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()), String.format(NOT_SYSTEM_CALLER_MSG, "call canProfileOwnerResetPasswordWhenLocked")); - if (Flags.resetPasswordWithTokenCoexistence()) { - return isAnyResetPasswordTokenActiveForUser(userId); - } synchronized (getLockObject()) { + if (Flags.resetPasswordWithTokenCoexistence()) { + return isAnyResetPasswordTokenActiveForUserLocked(userId); + } final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(userId); DevicePolicyData policy = getUserData(userId); if (poAdmin == null @@ -21120,26 +21122,29 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mPasswordTokenHandle, userId)) { return false; } - final ApplicationInfo poAppInfo; - try { - poAppInfo = mIPackageManager.getApplicationInfo( - poAdmin.info.getPackageName(), 0 /* flags */, userId); - } catch (RemoteException e) { - Slogf.e(LOG_TAG, "Failed to query PO app info", e); - return false; - } - if (poAppInfo == null) { - Slogf.wtf(LOG_TAG, "Cannot find AppInfo for profile owner"); - return false; - } - if (!poAppInfo.isEncryptionAware()) { - return false; - } - Slogf.d(LOG_TAG, "PO should be able to reset password from direct boot"); - return true; + return isEncryptionAware(poAdmin.info.getPackageName(), userId); } } + private boolean isEncryptionAware(String packageName, int userId) { + final ApplicationInfo poAppInfo; + try { + poAppInfo = mIPackageManager.getApplicationInfo(packageName, 0 /* flags */, userId); + } catch (RemoteException e) { + Slogf.e(LOG_TAG, "Failed to query PO / role holder's app info", e); + return false; + } + if (poAppInfo == null) { + Slogf.wtf(LOG_TAG, "Cannot find AppInfo for PO / role holder"); + return false; + } + if (!poAppInfo.isEncryptionAware()) { + return false; + } + Slogf.d(LOG_TAG, "PO / role holder should be able to reset password from direct boot"); + return true; + } + @Override public String getEnrollmentSpecificId(String callerPackage) { if (!mHasFeature) { diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java index 0881b4cf9bcf..e631cb66eaf7 100644 --- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -66,6 +66,7 @@ import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; +import android.os.SessionCreationConfig; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -200,7 +201,7 @@ public class HintManagerServiceTest { when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(SESSION_TIDS_C), eq(0L), anyInt(), any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2], - SESSION_IDS[2])); + SESSION_IDS[2])); when(mIPowerMock.getInterfaceVersion()).thenReturn(6); when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); @@ -305,6 +306,14 @@ public class HintManagerServiceTest { return mService; } + private SessionCreationConfig makeSessionCreationConfig(int[] tids, + long targetWorkDurationNanos) { + SessionCreationConfig config = new SessionCreationConfig(); + config.tids = tids; + config.targetWorkDurationNanos = targetWorkDurationNanos; + return config; + } + @Test public void testInitializeService() { HintManagerService service = createService(); @@ -316,12 +325,14 @@ public class HintManagerServiceTest { public void testCreateHintSessionInvalidPid() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(new int[]{TID, 1}, DEFAULT_TARGET_DURATION); // Make sure we throw exception when adding a TID doesn't belong to the processes // In this case, we add `init` PID into the list. SessionConfig config = new SessionConfig(); assertThrows(SecurityException.class, () -> service.getBinderServiceInstance().createHintSessionWithConfig(token, - new int[]{TID, 1}, DEFAULT_TARGET_DURATION, SessionTag.OTHER, config)); + SessionTag.OTHER, creationConfig, config)); } @Test @@ -329,17 +340,23 @@ public class HintManagerServiceTest { HintManagerService service = createService(); IBinder token = new Binder(); makeConfigCreationUnsupported(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, - SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); + SessionTag.OTHER, creationConfig, new SessionConfig()); assertNotNull(a); + creationConfig.tids = SESSION_TIDS_B; + creationConfig.targetWorkDurationNanos = DOUBLED_TARGET_DURATION; IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token, - SESSION_TIDS_B, DOUBLED_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); + SessionTag.OTHER, creationConfig, new SessionConfig()); assertNotEquals(a, b); + creationConfig.tids = SESSION_TIDS_C; + creationConfig.targetWorkDurationNanos = 0L; IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token, - SESSION_TIDS_C, 0L, SessionTag.OTHER, new SessionConfig()); + SessionTag.OTHER, creationConfig, new SessionConfig()); assertNotNull(c); verify(mNativeWrapperMock, times(3)).halCreateHintSession(anyInt(), anyInt(), any(int[].class), anyLong()); @@ -349,22 +366,28 @@ public class HintManagerServiceTest { public void testCreateHintSessionWithConfig() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); SessionConfig config = new SessionConfig(); IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, - SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, config); + SessionTag.OTHER, creationConfig, config); assertNotNull(a); assertEquals(SESSION_IDS[0], config.id); SessionConfig config2 = new SessionConfig(); + creationConfig.tids = SESSION_TIDS_B; + creationConfig.targetWorkDurationNanos = DOUBLED_TARGET_DURATION; IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token, - SESSION_TIDS_B, DOUBLED_TARGET_DURATION, SessionTag.APP, config2); + SessionTag.APP, creationConfig, config2); assertNotEquals(a, b); assertEquals(SESSION_IDS[1], config2.id); SessionConfig config3 = new SessionConfig(); + creationConfig.tids = SESSION_TIDS_C; + creationConfig.targetWorkDurationNanos = 0L; IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token, - SESSION_TIDS_C, 0L, SessionTag.GAME, config3); + SessionTag.GAME, creationConfig, config3); assertNotNull(c); assertEquals(SESSION_IDS[2], config3.id); verify(mNativeWrapperMock, times(3)).halCreateHintSessionWithConfig(anyInt(), anyInt(), @@ -372,13 +395,48 @@ public class HintManagerServiceTest { } @Test + public void testCreateGraphicsPipelineSessions() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + + final int threadCount = + service.getBinderServiceInstance().getMaxGraphicsPipelineThreadsCount(); + long sessionPtr1 = 1111L; + long sessionId1 = 11111L; + CountDownLatch stopLatch1 = new CountDownLatch(1); + int[] tids1 = createThreads(threadCount, stopLatch1); + when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1), + eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class))) + .thenAnswer(fakeCreateWithConfig(sessionPtr1, sessionId1)); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION); + + creationConfig.modesToEnable = new int[] {1}; // GRAPHICS_PIPELINE + + SessionConfig config = new SessionConfig(); + IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, + SessionTag.OTHER, creationConfig, config); + assertNotNull(a); + assertEquals(sessionId1, config.id); + + creationConfig.tids = createThreads(1, stopLatch1); + + assertThrows(IllegalArgumentException.class, () -> { + service.getBinderServiceInstance().createHintSessionWithConfig(token, + SessionTag.OTHER, creationConfig, config); + }); + } + + @Test public void testPauseResumeHintSession() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); // Set session to background and calling updateHintAllowedByProcState() would invoke // pause(); @@ -414,9 +472,11 @@ public class HintManagerServiceTest { public void testCloseHintSession() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, - SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); + SessionTag.OTHER, creationConfig, new SessionConfig()); a.close(); verify(mNativeWrapperMock, times(1)).halCloseHintSession(anyLong()); @@ -426,9 +486,11 @@ public class HintManagerServiceTest { public void testUpdateTargetWorkDuration() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, - SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); + SessionTag.OTHER, creationConfig, new SessionConfig()); assertThrows(IllegalArgumentException.class, () -> { a.updateTargetWorkDuration(-1L); @@ -446,10 +508,12 @@ public class HintManagerServiceTest { public void testReportActualWorkDuration() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); a.updateTargetWorkDuration(100L); a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE); @@ -489,10 +553,12 @@ public class HintManagerServiceTest { public void testSendHint() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET); verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(), @@ -516,10 +582,12 @@ public class HintManagerServiceTest { public void testDoHintInBackground() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); @@ -538,10 +606,12 @@ public class HintManagerServiceTest { public void testDoHintInForeground() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); @@ -552,10 +622,12 @@ public class HintManagerServiceTest { public void testSetThreads() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); a.updateTargetWorkDuration(100L); @@ -591,9 +663,11 @@ public class HintManagerServiceTest { when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1), eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class))) .thenReturn(sessionPtr1); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION); AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, tids1, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); assertNotNull(session1); // trigger UID state change by making the process foreground->background, but because the @@ -626,9 +700,11 @@ public class HintManagerServiceTest { when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1), eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class))) .thenReturn(sessionPtr1); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION); AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, tids1, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); assertNotNull(session1); // let all session 1 threads to exit and the cleanup should force pause the session 1 @@ -734,10 +810,12 @@ public class HintManagerServiceTest { public void testSetMode() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); a.setMode(0, true); verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(), @@ -746,12 +824,19 @@ public class HintManagerServiceTest { a.setMode(0, false); verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(), eq(0), eq(false)); + } - assertThrows(IllegalArgumentException.class, () -> { - a.setMode(-1, true); - }); + @Test + public void testSetModeSessionInBackGround() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); - reset(mNativeWrapperMock); // Set session to background, then the duration would not be updated. service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); @@ -763,6 +848,40 @@ public class HintManagerServiceTest { } @Test + public void testSetModeInvalid() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); + + assertThrows(IllegalArgumentException.class, () -> { + a.setMode(-1, true); + }); + } + + @Test + public void testSetModeUponSessionCreation() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + creationConfig.modesToEnable = new int[] {0, 1}; + + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); + assertNotNull(a); + verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(), + eq(0), eq(true)); + verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(), + eq(1), eq(true)); + } + + @Test public void testGetChannel() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); @@ -950,9 +1069,12 @@ public class HintManagerServiceTest { private void runAppHintSession(HintManagerService service, int logId, AtomicReference<Boolean> shouldRun) throws Exception { IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); // we will start some threads and get their valid TIDs to update int threadCount = 3; // the list of TIDs @@ -1017,10 +1139,12 @@ public class HintManagerServiceTest { public void testReportActualWorkDuration2() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); + SessionCreationConfig creationConfig = + makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, - SessionTag.OTHER, new SessionConfig()); + .createHintSessionWithConfig(token, SessionTag.OTHER, + creationConfig, new SessionConfig()); a.updateTargetWorkDuration(100L); a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index e6b4bc98ea4c..30af4ea7d9ef 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -17405,6 +17405,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) public void testPostPromotableNotification_noPermission() throws Exception { + mBinderService.setCanBePromoted(mPkg, mUid, false, true); + assertThat(mBinderService.appCanBePromoted(mPkg, mUid)).isFalse(); + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index dda060d5d586..80e86a15156a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -256,6 +256,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { return FlagsParameterization.allCombinationsOf( + android.app.Flags.FLAG_API_RICH_ONGOING, FLAG_NOTIFICATION_CLASSIFICATION, FLAG_MODES_UI); } @@ -6511,12 +6512,21 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + @DisableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) public void testNoAppHasPermissionToPromoteByDefault() { mHelper.setShowBadge(PKG_P, UID_P, true); assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse(); } @Test + @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING, + android.app.Flags.FLAG_UI_RICH_ONGOING}) + public void testAllAppsHavePermissionToPromoteByDefault() { + mHelper.setShowBadge(PKG_P, UID_P, true); + assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue(); + } + + @Test @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) public void testSetCanBePromoted() { mHelper.setCanBePromoted(PKG_P, UID_P, true, true); |