Merge "Add a new aconfig file for ADPF flags" into main
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 1c6e40e..963307b 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 @@
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 @@
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 b0784f1..a3eaefd 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.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 @@
* 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 9bb71ef..96968f5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -21098,6 +21098,7 @@
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 @@
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 bc38294..bdd7a37 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -236,6 +236,7 @@
"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 4bc5bd2..26308f6 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 @@
}
}
+ @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 @@
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 62b131a..9b37533 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.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 @@
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 @@
public IInputMethodSessionWrapper(Context context,
InputMethodSession inputMethodSession, InputChannel channel) {
+ mContext = context;
mCaller = new HandlerCaller(context, null,
this, true /*asyncHandler*/);
mInputMethodSession = inputMethodSession;
@@ -233,6 +241,8 @@
}
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 @@
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 @@
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 977c5bd..4bde8e2 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.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 @@
}
/**
+ * 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 3312055..a043da9 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -20,6 +20,7 @@
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 @@
* 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 @@
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 0000000..cdc0ef4
--- /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 2ef8764..5ac53f1 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -12,6 +12,15 @@
}
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 b7b8f0b1..e257f34 100644
--- a/core/java/android/text/style/TtsSpan.java
+++ b/core/java/android/text/style/TtsSpan.java
@@ -107,11 +107,13 @@
/**
* 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 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 19d3dc4..5b4b5300 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -110,7 +110,6 @@
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 @@
}
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 8a006fa..e60fc3a 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 {
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 4f48cb6..1806a83 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 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 deaf957..73abc47 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -184,3 +184,11 @@
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 1acde73..4723eb2 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 @@
)
}
- 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 @@
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 @@
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 @@
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 @@
}
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 @@
*/
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 @@
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 a0bdd9f..eb930b0 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.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.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 @@
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 @@
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 @@
wct = wct,
displayId = DEFAULT_DISPLAY,
excludeTaskId = taskId,
+ reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
wct.startTask(
taskId,
@@ -413,6 +422,7 @@
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 @@
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 @@
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 @@
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 @@
wct = wct,
displayId = displayId,
excludeTaskId = launchingTaskId,
+ reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
if (remoteTransition == null) {
val t = desktopMixedTransitionHandler.startLaunchTransition(
@@ -766,7 +788,10 @@
/** 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 @@
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 @@
// 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 @@
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 @@
}
// 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 @@
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 @@
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 6da4f51..d917f93 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 @@
}
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 1c58dbb..032dac9 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.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 @@
// only care about latest one.
mAnimApp = appThread;
- // TODO(b/366021931): Formalize this later
- final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition");
- if (isSyntheticRequest) {
- return startSyntheticRecentsTransition(listener);
- } else {
- return startRealRecentsTransition(intent, fillIn, options, listener);
+ 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.getBoolean(
+ "is_synthetic_recents_transition", /* defaultValue= */ false);
+ final IBinder transition;
+ if (isSyntheticRequest) {
+ transition = startSyntheticRecentsTransition(listener);
+ } else {
+ transition = startRealRecentsTransition(intent, fillIn, options, listener);
+ }
+ return transition;
}
/**
@@ -542,7 +551,7 @@
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 @@
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 @@
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 95874c8..ea7cfd3 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 7b71e41..b8c9151 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.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 @@
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 456f2c0..8a2b394 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.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 @@
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 8b6aaaf..4a01e8e 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.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 @@
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 @@
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 @@
width: Int,
height: Int,
flags: Int,
- @LayoutRes layoutId: Int
+ @LayoutRes layoutId: Int,
+ ignoreCutouts: Boolean = false
) : this(
windowManagerWrapper = windowManagerWrapper,
taskId = taskId,
@@ -72,7 +79,8 @@
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 @@
y: Int,
width: Int,
height: Int,
- flags: Int
+ flags: Int,
+ ignoreCutouts: Boolean = false
) : this(
windowManagerWrapper = windowManagerWrapper,
taskId = taskId,
@@ -92,7 +101,8 @@
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 5f25f42..3f65d93 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 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 @@
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 f935ac7..9c31b46 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 @@
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 @@
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 @@
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 a4f4d05..4666276 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.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 @@
immersive = true
)
- controller.moveTaskToNonImmersive(task)
+ controller.moveTaskToNonImmersive(task, USER_INTERACTION)
controller.onTransitionReady(
transition = mockBinder,
info = createTransitionInfo(
@@ -195,7 +196,7 @@
)
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
immersive = false
)
- controller.exitImmersiveIfApplicable(wct, task)
+ controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION)
assertThat(wct.hasBoundsChange(task.token)).isFalse()
}
@@ -409,7 +411,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 5df3957..490e6b9 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.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 @@
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 @@
controller.minimizeTask(task)
- verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task))
+ verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any())
}
@Test
@@ -1803,7 +1805,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
controller.toggleDesktopTaskFullImmersiveState(task)
- verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(task)
+ verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any())
}
@Test
@@ -3752,7 +3755,7 @@
task.requestedVisibleTypes = WindowInsets.Type.statusBars()
controller.onTaskInfoChanged(task)
- verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(task)
+ verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any())
}
@Test
@@ -3764,7 +3767,20 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 6087763..f0f5fe1 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.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.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.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.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 @@
private IRecentTasksListener mRecentTasksListener;
@Mock
private TaskStackTransitionObserver mTaskStackTransitionObserver;
+ @Mock
+ private Transitions mTransitions;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -129,10 +147,9 @@
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 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 @@
@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 @@
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 6b41ddd..9da7bec 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -355,12 +355,14 @@
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 @@
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 @@
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 bc1945e..c67e93c 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// ===================================== 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 @@
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 @@
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 @@
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 @@
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 f707a0e..c5fb808 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 @@
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 @@
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 @@
APerformanceHint_getRateLimiterPropertiesForTesting(&mMaxLoadHintsPerInterval,
&mLoadHintInterval);
APerformanceHint_setIHintManagerForTesting(&mMockIHintManager);
+ APerformanceHint_setUseGraphicsPipelineForTesting(true);
APerformanceHint_setUseNewLoadHintBehaviorForTesting(true);
}
@@ -118,21 +124,22 @@
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 @@
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, 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 @@
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 f3820e5..8c028bc 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 @@
/**
* 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 @@
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 @@
}
// 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 bb0b190..55d2de2 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 @@
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 7c9d9c5..76049ca 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 @@
return;
}
- if (Flags.offloadApi()) {
+ if (Flags.offloadApi() && Flags.offloadImplementation()) {
HubInfoRegistry registry;
try {
registry = new HubInfoRegistry(mContextHubWrapper);
@@ -527,8 +527,8 @@
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 e6f784c..749952e 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 @@
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 @@
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 @@
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 @@
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 @@
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 17459df..71f67d8 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.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.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 @@
@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 @@
@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 @@
*/
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 @@
mActiveSessions = new ArrayMap<>();
mChannelMap = new ArrayMap<>();
mSessionSnapshotMap = new ArrayMap<>();
+ mThreadsUsageMap = new ArrayMap<>();
mNativeWrapper = injector.createNativeWrapper();
mNativeWrapper.halInit();
mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
@@ -357,6 +373,36 @@
}
}
+ 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 @@
int mMaxConcurrentSession;
int mMaxThreadCount;
int mPowerEfficientSessionCount;
+ int mGraphicsPipelineSessionCount;
final int mTargetDurationNsCountPQSize = 100;
PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ;
@@ -425,6 +472,7 @@
mMaxConcurrentSession = 0;
mMaxThreadCount = 0;
mPowerEfficientSessionCount = 0;
+ mGraphicsPipelineSessionCount = 0;
mTargetDurationNsCountPQ = new PriorityQueue<>(1);
}
@@ -443,6 +491,10 @@
mPowerEfficientSessionCount += 1;
}
+ void logGraphicsPipelineSession() {
+ mGraphicsPipelineSessionCount += 1;
+ }
+
void updateThreadCount(int threadCount) {
mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
}
@@ -473,6 +525,10 @@
return mPowerEfficientSessionCount;
}
+ int getGraphicsPipelineSessionCount() {
+ return mGraphicsPipelineSessionCount;
+ }
+
long[] targetDurationNsList() {
final int listSize = 5;
long[] targetDurations = new long[listSize];
@@ -1163,24 +1219,40 @@
+ " 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 @@
.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 @@
}
sessionSet.add(hs);
mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid);
-
- return hs;
}
+
+ 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);
+ }
+ }
+
+ 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 @@
}
@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 @@
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 @@
}
}
+ 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
}
}
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 @@
+ " 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 boolean isGraphicsPipeline() {
+ synchronized (this) {
+ return mGraphicsPipeline;
+ }
+ }
+
public int getUid() {
return mUid;
}
@@ -1964,6 +2198,7 @@
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 6292cbf..dde213d 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 @@
}
}
- 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 @@
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 @@
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 0881b4c..e631cb6 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.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 @@
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 @@
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 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 @@
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 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 @@
}
@Test
- public void testPauseResumeHintSession() throws Exception {
+ 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 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 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 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 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 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 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 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 @@
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 @@
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 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 @@
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);
- reset(mNativeWrapperMock);
+ AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
+
// 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 @@
}
@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 @@
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 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 e6b4bc9..30af4ea 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 @@
@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 dda060d..80e86a1 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 @@
@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 @@
@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);