Merge "Check system and phone UID in multiple-user-aware way" into main
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 099dbbc..2ca9f2e 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1982,6 +1982,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public float getRs2Value();
method public int getStreamMinVolumeInt(int);
method @NonNull public java.util.Map<java.lang.Integer,java.lang.Boolean> getSurroundFormats();
+ method @NonNull public android.media.VolumePolicy getVolumePolicy();
method public boolean hasRegisteredDynamicPolicy();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean isCsdEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public boolean isFullVolumeDevice();
@@ -2073,6 +2074,20 @@
method public android.media.PlaybackParams setAudioStretchMode(int);
}
+ public final class VolumePolicy implements android.os.Parcelable {
+ ctor public VolumePolicy(boolean, boolean, boolean, int);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int A11Y_MODE_INDEPENDENT_A11Y_VOLUME = 1; // 0x1
+ field public static final int A11Y_MODE_MEDIA_A11Y_VOLUME = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.VolumePolicy> CREATOR;
+ field @NonNull public static final android.media.VolumePolicy DEFAULT;
+ field public final boolean doNotDisturbWhenSilent;
+ field public final int vibrateToSilentDebounce;
+ field public final boolean volumeDownToEnterSilent;
+ field public final boolean volumeUpToExitSilent;
+ }
+
public static final class VolumeShaper.Configuration.Builder {
method @NonNull public android.media.VolumeShaper.Configuration.Builder setOptionFlags(int);
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index aca9bb4..531537c 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -30,6 +30,7 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
import android.os.Parcel;
@@ -303,6 +304,19 @@
public boolean isTopActivityStyleFloating;
/**
+ * The URI of the intent that generated the top-most activity opened using a URL.
+ * @hide
+ */
+ @Nullable
+ public Uri capturedLink;
+
+ /**
+ * The time of the last launch of the activity opened using the {@link #capturedLink}.
+ * @hide
+ */
+ public long capturedLinkTimestamp;
+
+ /**
* Encapsulate specific App Compat information.
* @hide
*/
@@ -436,6 +450,8 @@
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
&& isTopActivityStyleFloating == that.isTopActivityStyleFloating
+ && Objects.equals(capturedLink, that.capturedLink)
+ && capturedLinkTimestamp == that.capturedLinkTimestamp
&& appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo);
}
@@ -506,6 +522,8 @@
displayAreaFeatureId = source.readInt();
isTopActivityTransparent = source.readBoolean();
isTopActivityStyleFloating = source.readBoolean();
+ capturedLink = source.readTypedObject(Uri.CREATOR);
+ capturedLinkTimestamp = source.readLong();
appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
}
@@ -554,6 +572,8 @@
dest.writeInt(displayAreaFeatureId);
dest.writeBoolean(isTopActivityTransparent);
dest.writeBoolean(isTopActivityStyleFloating);
+ dest.writeTypedObject(capturedLink, flags);
+ dest.writeLong(capturedLinkTimestamp);
dest.writeTypedObject(appCompatTaskInfo, flags);
}
@@ -592,6 +612,8 @@
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " isTopActivityTransparent=" + isTopActivityTransparent
+ " isTopActivityStyleFloating=" + isTopActivityStyleFloating
+ + " capturedLink=" + capturedLink
+ + " capturedLinkTimestamp=" + capturedLinkTimestamp
+ " appCompatTaskInfo=" + appCompatTaskInfo
+ "}";
}
diff --git a/core/java/android/app/admin/OWNERS b/core/java/android/app/admin/OWNERS
index 308f1d6..4f3f5d9 100644
--- a/core/java/android/app/admin/OWNERS
+++ b/core/java/android/app/admin/OWNERS
@@ -1,7 +1,6 @@
# Bug component: 142675
# Assign bugs to device-policy-manager-triage@google.com
-file:WorkDeviceExperience_OWNERS
file:EnterprisePlatformSecurity_OWNERS
yamasani@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/assist/OWNERS b/core/java/android/app/assist/OWNERS
index e4ffd7f..b53bdc2 100644
--- a/core/java/android/app/assist/OWNERS
+++ b/core/java/android/app/assist/OWNERS
@@ -1,2 +1 @@
-hackz@google.com
-volnov@google.com
\ No newline at end of file
+srazdan@google.com
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index 5953890..93724bb 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -136,6 +136,14 @@
"android.intent.extra.LOGGING_INSTANCE_ID";
/**
+ * The id of the task containing the window that initiated the drag that should be hidden.
+ * Only provided to internal drag handlers as a part of the DRAG_START event.
+ * @hide
+ */
+ public static final String EXTRA_HIDE_DRAG_SOURCE_TASK_ID =
+ "android.intent.extra.HIDE_DRAG_SOURCE_TASK_ID";
+
+ /**
* Indicates that a ClipData contains potentially sensitive information, such as a
* password or credit card number.
* <p>
diff --git a/core/java/android/hardware/OWNERS b/core/java/android/hardware/OWNERS
index 51ad151..43d3f54 100644
--- a/core/java/android/hardware/OWNERS
+++ b/core/java/android/hardware/OWNERS
@@ -5,7 +5,7 @@
sumir@google.com
# Camera
-per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,zhijunhe@google.com,jchowdhary@google.com
+per-file *Camera*=file:platform/frameworks/av:/camera/OWNERS
# Sensor Privacy
per-file *SensorPrivacy* = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index acd0d00f..16d9ef2 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -61,3 +61,10 @@
description: "Allows system to provide keyboard specific key drawables and shortcuts via config files"
bug: "345440920"
}
+
+flag {
+ namespace: "input_native"
+ name: "keyboard_a11y_mouse_keys"
+ description: "Controls if the mouse keys accessibility feature for physical keyboard is available to the user"
+ bug: "341799888"
+}
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 7353d76..d934970 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -295,24 +295,38 @@
@NonNull
@Override
public String toString() {
- String retVal = "";
- retVal += "ID/handle : " + mId;
- retVal += ", Name : " + mName;
- retVal += "\n\tVendor : " + mVendor;
- retVal += ", Toolchain : " + mToolchain;
- retVal += ", Toolchain version: 0x" + Integer.toHexString(mToolchainVersion);
- retVal += "\n\tPlatformVersion : 0x" + Integer.toHexString(mPlatformVersion);
- retVal += ", SwVersion : "
- + Byte.toUnsignedInt(mChreApiMajorVersion) + "." + Byte.toUnsignedInt(
- mChreApiMinorVersion) + "." + Short.toUnsignedInt(mChrePatchVersion);
- retVal += ", CHRE platform ID: 0x" + Long.toHexString(mChrePlatformId);
- retVal += "\n\tPeakMips : " + mPeakMips;
- retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
- retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
- retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
- retVal += ", SupportsReliableMessage : " + mSupportsReliableMessages;
-
- return retVal;
+ StringBuilder out = new StringBuilder();
+ out.append("ID/handle : ");
+ out.append(mId);
+ out.append(", Name : ");
+ out.append(mName);
+ out.append("\n\tVendor : ");
+ out.append(mVendor);
+ out.append(", Toolchain : ");
+ out.append(mToolchain);
+ out.append(", Toolchain version: 0x");
+ out.append(Integer.toHexString(mToolchainVersion));
+ out.append("\n\tPlatformVersion : 0x");
+ out.append(Integer.toHexString(mPlatformVersion));
+ out.append(", SwVersion : ");
+ out.append(Byte.toUnsignedInt(mChreApiMajorVersion));
+ out.append(".");
+ out.append(Byte.toUnsignedInt(mChreApiMinorVersion));
+ out.append(".");
+ out.append(Short.toUnsignedInt(mChrePatchVersion));
+ out.append(", CHRE platform ID: 0x");
+ out.append(Long.toHexString(mChrePlatformId));
+ out.append("\n\tPeakMips : ");
+ out.append(mPeakMips);
+ out.append(", StoppedPowerDraw : ");
+ out.append(mStoppedPowerDrawMw);
+ out.append(" mW, PeakPowerDraw : ");
+ out.append(mPeakPowerDrawMw);
+ out.append(" mW, MaxPacketLength : ");
+ out.append(mMaxPacketLengthBytes);
+ out.append(" Bytes, SupportsReliableMessages : ");
+ out.append(mSupportsReliableMessages);
+ return out.toString();
}
/**
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 85a5d45..ec0adda 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -230,27 +230,38 @@
public String toString() {
int length = mMessageBody.length;
- String ret = "NanoAppMessage[type = " + mMessageType + ", length = " + mMessageBody.length
- + " bytes, " + (mIsBroadcasted ? "broadcast" : "unicast") + ", nanoapp = 0x"
- + Long.toHexString(mNanoAppId) + ", isReliable = "
- + (mIsReliable ? "true" : "false") + ", messageSequenceNumber = "
- + mMessageSequenceNumber + "](";
+ StringBuilder out = new StringBuilder();
+ out.append( "NanoAppMessage[type = ");
+ out.append(mMessageType);
+ out.append(", length = ");
+ out.append(mMessageBody.length);
+ out.append(" bytes, ");
+ out.append(mIsBroadcasted ? "broadcast" : "unicast");
+ out.append(", nanoapp = 0x");
+ out.append(Long.toHexString(mNanoAppId));
+ out.append(", isReliable = ");
+ out.append(mIsReliable ? "true" : "false");
+ out.append(", messageSequenceNumber = ");
+ out.append(mMessageSequenceNumber);
+ out.append("](");
+
if (length > 0) {
- ret += "data = 0x";
+ out.append("data = 0x");
}
for (int i = 0; i < Math.min(length, DEBUG_LOG_NUM_BYTES); i++) {
- ret += HexEncoding.encodeToString(mMessageBody[i], true /* upperCase */);
+ out.append(HexEncoding.encodeToString(mMessageBody[i],
+ true /* upperCase */));
if ((i + 1) % 4 == 0) {
- ret += " ";
+ out.append(" ");
}
}
if (length > DEBUG_LOG_NUM_BYTES) {
- ret += "...";
+ out.append("...");
}
- ret += ")";
+ out.append(")");
- return ret;
+ return out.toString();
}
@Override
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
index 93c5439..4bb66ed 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
@@ -17,6 +17,7 @@
package android.inputmethodservice.navigationbar;
import android.annotation.ColorInt;
+import android.graphics.Color;
final class NavigationBarConstants {
private NavigationBarConstants() {
@@ -27,13 +28,13 @@
// TODO(b/215443343): Handle this in the drawable then remove this constant.
static final float NAVBAR_BACK_BUTTON_IME_OFFSET = 2.0f;
- // Copied from "light_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
+ // Copied from "white" at packages/SettingsLib/res/values/colors.xml
@ColorInt
- static final int LIGHT_MODE_ICON_COLOR_SINGLE_TONE = 0xffffffff;
+ static final int WHITE = Color.WHITE;
- // Copied from "dark_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
+ // Copied from "black" at packages/SettingsLib/res/values/colors.xml
@ColorInt
- static final int DARK_MODE_ICON_COLOR_SINGLE_TONE = 0x99000000;
+ static final int BLACK = Color.BLACK;
// Copied from "navigation_bar_deadzone_hold"
static final int NAVIGATION_BAR_DEADZONE_HOLD = 333;
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
index e28f345..b522e9b 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -16,8 +16,8 @@
package android.inputmethodservice.navigationbar;
-import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE;
-import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.BLACK;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.WHITE;
import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET;
import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -83,8 +83,8 @@
super(context, attrs);
mLightContext = context;
- mLightIconColor = LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
- mDarkIconColor = DARK_MODE_ICON_COLOR_SINGLE_TONE;
+ mLightIconColor = WHITE;
+ mDarkIconColor = BLACK;
mConfiguration = new Configuration();
mTmpLastConfiguration = new Configuration();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c954cdb..2562c8e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12324,6 +12324,18 @@
"accessibility_force_invert_color_enabled";
/**
+ * Whether to enable mouse keys for Physical Keyboard accessibility.
+ *
+ * If set to true, key presses (of the mouse keys) on
+ * physical keyboard will control mouse pointer on the display.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String ACCESSIBILITY_MOUSE_KEYS_ENABLED =
+ "accessibility_mouse_keys_enabled";
+
+ /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
diff --git a/core/java/android/util/SequenceUtils.java b/core/java/android/util/SequenceUtils.java
index f833ce3..4f8db0f 100644
--- a/core/java/android/util/SequenceUtils.java
+++ b/core/java/android/util/SequenceUtils.java
@@ -25,8 +25,8 @@
* {@link #getInitSeq}.
* 2. Whenever a newer info needs to be sent to the client side, the system server should first
* update its seq with {@link #getNextSeq}, then send the new info with the new seq to the client.
- * 3. On the client side, when receiving a new info, it should only consume it if it is newer than
- * the last received info seq by checking {@link #isIncomingSeqNewer}.
+ * 3. On the client side, when receiving a new info, it should only consume it if it is not stale by
+ * checking {@link #isIncomingSeqStale}.
*
* @hide
*/
@@ -36,15 +36,22 @@
}
/**
- * Returns {@code true} if the incomingSeq is newer than the curSeq.
+ * Returns {@code true} if the incomingSeq is stale, which means the client should not consume
+ * it.
*/
- public static boolean isIncomingSeqNewer(int curSeq, int incomingSeq) {
+ public static boolean isIncomingSeqStale(int curSeq, int incomingSeq) {
+ if (curSeq == getInitSeq()) {
+ // curSeq can be set to the initial seq in the following cases:
+ // 1. The client process/field is newly created/recreated.
+ // 2. The field is not managed by the system server, such as WindowlessWindowManager.
+ // The client should always consume the incoming in these cases.
+ return false;
+ }
// Convert to long for comparison.
final long diff = (long) incomingSeq - curSeq;
- // If there has been a sufficiently large jump, assume the sequence has wrapped around.
- // For example, when the last seq is MAX_VALUE, the incoming seq will be MIN_VALUE + 1.
- // diff = MIN_VALUE + 1 - MAX_VALUE. It is smaller than 0, but should be treated as newer.
- return diff > 0 || diff < Integer.MIN_VALUE;
+ // When diff is 0, allow client to consume.
+ // When there has been a sufficiently large jump, assume the sequence has wrapped around.
+ return (diff < 0 && diff > Integer.MIN_VALUE) || diff > Integer.MAX_VALUE;
}
/** Returns the initial seq. */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4766942..f77e219 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5512,6 +5512,14 @@
public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 1 << 13;
/**
+ * Flag indicating that this drag will result in the caller activity's task to be hidden for the
+ * duration of the drag, this means that the source activity will not receive drag events for
+ * the current drag gesture. Only the current voice interaction service may use this flag.
+ * @hide
+ */
+ public static final int DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START = 1 << 14;
+
+ /**
* Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
*/
private float mVerticalScrollFactor;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 88dc3f4..07cbaa9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -23,7 +23,7 @@
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
import static android.os.Trace.TRACE_TAG_VIEW;
import static android.util.SequenceUtils.getInitSeq;
-import static android.util.SequenceUtils.isIncomingSeqNewer;
+import static android.util.SequenceUtils.isIncomingSeqStale;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_LOCATION;
@@ -1207,6 +1207,8 @@
private final Rect mChildBoundingInsets = new Rect();
private boolean mChildBoundingInsetsChanged = false;
+ private final boolean mDisableDrawWakeLock;
+
private String mTag = TAG;
private String mFpsTraceName;
private String mLargestViewTraceName;
@@ -1336,6 +1338,10 @@
}
mAppStartInfoTimestampsFlagValue = android.app.Flags.appStartInfoTimestamps();
+
+ // Disable DRAW_WAKE_LOCK starting U.
+ mDisableDrawWakeLock =
+ CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK) && disableDrawWakeLock();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -2329,23 +2335,23 @@
if (mLastReportedFrames == null) {
return;
}
- if (isIncomingSeqNewer(mLastReportedFrames.seq, inOutFrames.seq)) {
+ if (isIncomingSeqStale(mLastReportedFrames.seq, inOutFrames.seq)) {
+ // If the incoming is stale, use the last reported instead.
+ inOutFrames.setTo(mLastReportedFrames);
+ } else {
// Keep track of the latest.
mLastReportedFrames.setTo(inOutFrames);
- } else {
- // If the last reported frames is newer, use the last reported instead.
- inOutFrames.setTo(mLastReportedFrames);
}
}
private void onInsetsStateChanged(@NonNull InsetsState insetsState) {
if (insetsControlSeq()) {
- if (isIncomingSeqNewer(mLastReportedInsetsStateSeq, insetsState.getSeq())) {
- mLastReportedInsetsStateSeq = insetsState.getSeq();
- } else {
- // The last reported InsetsState is newer. Skip.
+ if (isIncomingSeqStale(mLastReportedInsetsStateSeq, insetsState.getSeq())) {
+ // The incoming is stale. Skip.
return;
}
+ // Keep track of the latest.
+ mLastReportedInsetsStateSeq = insetsState.getSeq();
}
if (mTranslator != null) {
@@ -2362,13 +2368,13 @@
}
if (insetsControlSeq()) {
- if (isIncomingSeqNewer(mLastReportedActiveControlsSeq, activeControls.getSeq())) {
- mLastReportedActiveControlsSeq = activeControls.getSeq();
- } else {
- // The last reported controls is newer. Skip.
+ if (isIncomingSeqStale(mLastReportedActiveControlsSeq, activeControls.getSeq())) {
+ // The incoming is stale. Skip.
activeControls.release();
return;
}
+ // Keep track of the latest.
+ mLastReportedActiveControlsSeq = activeControls.getSeq();
}
final InsetsSourceControl[] controls = activeControls.get();
@@ -2472,11 +2478,7 @@
void pokeDrawLockIfNeeded() {
// Disable DRAW_WAKE_LOCK starting U. Otherwise, only need to acquire it for DOZE state.
- if (CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK) && disableDrawWakeLock()) {
- return;
- }
-
- if (mAttachInfo.mDisplayState != Display.STATE_DOZE) {
+ if (mDisableDrawWakeLock || mAttachInfo.mDisplayState != Display.STATE_DOZE) {
// In DOZE_SUSPEND, Android shouldn't control the display; therefore we only poke the
// draw wake lock when display state is DOZE. Noted that Display#isDozeState includes
// DOZE_SUSPEND as well, so, it's not feasible here.
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index f4f6c8a..cd033ca 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -25,6 +25,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -44,6 +45,8 @@
import android.view.SurfaceControl;
import android.view.WindowInsets.Type.InsetsType;
+import com.android.window.flags.Flags;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -962,6 +965,23 @@
}
/**
+ * Sets the task as trimmable or not. This can be used to prevent the task from being trimmed by
+ * recents. This attribute is set to true on task creation by default.
+ *
+ * @param isTrimmableFromRecents When {@code true}, task is set as trimmable from recents.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setTaskTrimmableFromRecents(
+ @NonNull WindowContainerToken container,
+ boolean isTrimmableFromRecents) {
+ mHierarchyOps.add(
+ HierarchyOp.createForSetTaskTrimmableFromRecents(container.asBinder(),
+ isTrimmableFromRecents));
+ return this;
+ }
+
+ /**
* Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving
* other in an unusable state. When false, other is left alone, but
@@ -1412,6 +1432,7 @@
public static final int HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH = 16;
public static final int HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION = 17;
public static final int HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK = 18;
+ public static final int HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE = 19;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1473,6 +1494,8 @@
private boolean mReparentLeafTaskIfRelaunch;
+ private boolean mIsTrimmableFromRecents;
+
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1575,6 +1598,16 @@
.build();
}
+ /** Create a hierarchy op for setting a task non-trimmable by recents. */
+ @FlaggedApi(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ public static HierarchyOp createForSetTaskTrimmableFromRecents(@NonNull IBinder container,
+ boolean isTrimmableFromRecents) {
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE)
+ .setContainer(container)
+ .setIsTrimmableFromRecents(isTrimmableFromRecents)
+ .build();
+ }
+
/** Only creates through {@link Builder}. */
private HierarchyOp(int type) {
mType = type;
@@ -1599,6 +1632,7 @@
mShortcutInfo = copy.mShortcutInfo;
mAlwaysOnTop = copy.mAlwaysOnTop;
mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch;
+ mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents;
}
protected HierarchyOp(Parcel in) {
@@ -1620,6 +1654,7 @@
mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
mAlwaysOnTop = in.readBoolean();
mReparentLeafTaskIfRelaunch = in.readBoolean();
+ mIsTrimmableFromRecents = in.readBoolean();
}
public int getType() {
@@ -1715,6 +1750,12 @@
return mIncludingParents;
}
+ /** Set the task to be trimmable */
+ @NonNull
+ public boolean isTrimmableFromRecents() {
+ return mIsTrimmableFromRecents;
+ }
+
/** Gets a string representation of a hierarchy-op type. */
public static String hopToString(int type) {
switch (type) {
@@ -1811,6 +1852,10 @@
sb.append("fragmentToken= ").append(mContainer)
.append(" operation= ").append(mTaskFragmentOperation);
break;
+ case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE:
+ sb.append("container= ").append(mContainer)
+ .append(" isTrimmable= ")
+ .append(mIsTrimmableFromRecents);
default:
sb.append("container=").append(mContainer)
.append(" reparent=").append(mReparent)
@@ -1841,6 +1886,7 @@
dest.writeTypedObject(mShortcutInfo, flags);
dest.writeBoolean(mAlwaysOnTop);
dest.writeBoolean(mReparentLeafTaskIfRelaunch);
+ dest.writeBoolean(mIsTrimmableFromRecents);
}
@Override
@@ -1910,6 +1956,8 @@
private boolean mReparentLeafTaskIfRelaunch;
+ private boolean mIsTrimmableFromRecents;
+
Builder(int type) {
mType = type;
}
@@ -2000,6 +2048,11 @@
return this;
}
+ Builder setIsTrimmableFromRecents(boolean isTrimmableFromRecents) {
+ mIsTrimmableFromRecents = isTrimmableFromRecents;
+ return this;
+ }
+
HierarchyOp build() {
final HierarchyOp hierarchyOp = new HierarchyOp(mType);
hierarchyOp.mContainer = mContainer;
@@ -2023,6 +2076,7 @@
hierarchyOp.mBounds = mBounds;
hierarchyOp.mIncludingParents = mIncludingParents;
hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
+ hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents;
return hierarchyOp;
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 7432ca7..245c0e7 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -122,6 +122,13 @@
}
flag {
+ name: "enable_caption_compat_inset_force_consumption"
+ namespace: "lse_desktop_experience"
+ description: "Enables force-consumption of caption bar insets for immersive apps in freeform"
+ bug: "316231589"
+}
+
+flag {
name: "show_desktop_windowing_dev_option"
namespace: "lse_desktop_experience"
description: "Whether to show developer option for enabling desktop windowing mode"
@@ -141,3 +148,10 @@
description: "Whether to enable desktop windowing transition handler and observer instead of task listeners."
bug: "332682201"
}
+
+flag {
+ name: "enable_desktop_windowing_multi_instance_features"
+ namespace: "lse_desktop_experience"
+ description: "Whether to enable multi-instance support in desktop windowing."
+ bug: "336289597"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 621b2c4..6b24545 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -70,17 +70,6 @@
}
flag {
- name: "skip_sleeping_when_switching_display"
- namespace: "windowing_frontend"
- description: "Reduce unnecessary visibility or lifecycle changes when changing fold state"
- bug: "303241079"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "introduce_smoother_dimmer"
namespace: "windowing_frontend"
description: "Refactor dim to fix flickers"
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 1638699..5c818f1 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -175,9 +175,17 @@
/** Track launching a dialog from a status bar chip. */
public static final int CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP = 111;
+ /** Track Launcher Keyboard Quick Switch View opening animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN = 112;
+
+ /** Track Launcher Keyboard Quick Switch View closing animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE = 113;
+
+ /** Track launching an app through the Launcher Keyboard Quick Switch View */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH = 114;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
/** @hide */
@IntDef({
@@ -280,7 +288,10 @@
CUJ_DESKTOP_MODE_EXIT_MODE,
CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
CUJ_DESKTOP_MODE_DRAG_WINDOW,
- CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP
+ CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -394,6 +405,9 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_DRAG_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_DRAG_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
}
private Cuj() {
@@ -612,6 +626,12 @@
return "DESKTOP_MODE_DRAG_WINDOW";
case CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP:
return "STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN:
+ return "CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE:
+ return "CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH:
+ return "CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index d244874..572a599 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -48,6 +48,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
@@ -65,7 +66,7 @@
private final String mLegacyViewerConfigFilename;
private final TraceBuffer mBuffer;
private final LegacyProtoLogViewerConfigReader mViewerConfig;
- private final TreeMap<String, IProtoLogGroup> mLogGroups;
+ private final Map<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
private final Runnable mCacheUpdater;
private final int mPerChunkSize;
@@ -74,20 +75,19 @@
private final Object mProtoLogEnabledLock = new Object();
public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
- TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
+ Runnable cacheUpdater) {
this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
- new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups, cacheUpdater);
+ new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, cacheUpdater);
}
public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
- TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
+ Runnable cacheUpdater) {
mLogFile = file;
mBuffer = new TraceBuffer(bufferCapacity);
mLegacyViewerConfigFilename = viewerConfigFilename;
mViewerConfig = viewerConfig;
mPerChunkSize = perChunkSize;
- mLogGroups = logGroups;
mCacheUpdater = cacheUpdater;
}
@@ -97,21 +97,26 @@
@VisibleForTesting
@Override
public void log(LogLevel level, IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable String messageString, Object[] args) {
+ @Nullable Object[] args) {
if (group.isLogToProto()) {
logToProto(messageHash, paramsMask, args);
}
if (group.isLogToLogcat()) {
- logToLogcat(group.getTag(), level, messageHash, messageString, args);
+ logToLogcat(group.getTag(), level, messageHash, args);
}
}
+ @Override
+ public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
+ // This will be removed very soon so no point implementing it here.
+ throw new IllegalStateException(
+ "Not implemented. Only implemented for PerfettoProtoLogImpl.");
+ }
+
private void logToLogcat(String tag, LogLevel level, long messageHash,
- @Nullable String messageString, Object[] args) {
+ @Nullable Object[] args) {
String message = null;
- if (messageString == null) {
- messageString = mViewerConfig.getViewerString(messageHash);
- }
+ final String messageString = mViewerConfig.getViewerString(messageHash);
if (messageString != null) {
if (args != null) {
try {
@@ -125,8 +130,10 @@
}
if (message == null) {
StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")");
- for (Object o : args) {
- builder.append(" ").append(o);
+ if (args != null) {
+ for (Object o : args) {
+ builder.append(" ").append(o);
+ }
}
message = builder.toString();
}
@@ -410,5 +417,12 @@
// so we ignore the level argument to this function.
return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
}
+
+ @Override
+ public void registerGroups(IProtoLogGroup... protoLogGroups) {
+ for (IProtoLogGroup group : protoLogGroups) {
+ mLogGroups.put(group.name(), group);
+ }
+ }
}
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
new file mode 100644
index 0000000..ebdad6d
--- /dev/null
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 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 com.android.internal.protolog;
+
+import static com.android.internal.protolog.ProtoLog.REQUIRE_PROTOLOGTOOL;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.protolog.common.ILogger;
+import com.android.internal.protolog.common.IProtoLog;
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogLevel;
+
+/**
+ * Class only create and used to server temporarily for when there is source code pre-processing by
+ * the ProtoLog tool, when the tracing to Perfetto flag is off, and the static REQUIRE_PROTOLOGTOOL
+ * boolean is false. In which case we simply want to log protolog message to logcat. Note, that this
+ * means that in such cases there is no real advantage of using protolog over logcat.
+ *
+ * @deprecated Should not be used. This is just a temporary class to support a legacy behavior.
+ */
+@Deprecated
+public class LogcatOnlyProtoLogImpl implements IProtoLog {
+ @Override
+ public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
+ Object[] args) {
+ throw new RuntimeException("Not supported when using LogcatOnlyProtoLogImpl");
+ }
+
+ @Override
+ public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object[] args) {
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new RuntimeException(
+ "REQUIRE_PROTOLOGTOOL not set to false before the first log call.");
+ }
+
+ String formattedString = TextUtils.formatSimple(messageString, args);
+ switch (logLevel) {
+ case VERBOSE -> Log.v(group.getTag(), formattedString);
+ case INFO -> Log.i(group.getTag(), formattedString);
+ case DEBUG -> Log.d(group.getTag(), formattedString);
+ case WARN -> Log.w(group.getTag(), formattedString);
+ case ERROR -> Log.e(group.getTag(), formattedString);
+ case WTF -> Log.wtf(group.getTag(), formattedString);
+ }
+ }
+
+ @Override
+ public boolean isProtoEnabled() {
+ return false;
+ }
+
+ @Override
+ public int startLoggingToLogcat(String[] groups, ILogger logger) {
+ return 0;
+ }
+
+ @Override
+ public int stopLoggingToLogcat(String[] groups, ILogger logger) {
+ return 0;
+ }
+
+ @Override
+ public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+ return true;
+ }
+
+ @Override
+ public void registerGroups(IProtoLogGroup... protoLogGroups) {
+ // Does nothing
+ }
+}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 42fa6ac..07be700 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -42,11 +42,11 @@
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.SEQ_NEEDS_INCREMENTAL_STATE;
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData;
import android.os.ShellCommand;
import android.os.SystemClock;
-import android.os.Trace;
import android.text.TextUtils;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
@@ -72,37 +72,45 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
/**
* A service for the ProtoLog logging system.
*/
public class PerfettoProtoLogImpl implements IProtoLog {
private static final String LOG_TAG = "ProtoLog";
+ public static final String NULL_STRING = "null";
private final AtomicInteger mTracingInstances = new AtomicInteger();
private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
this::onTracingInstanceStart,
- this::dumpTransitionTraceConfig,
+ this::onTracingFlush,
this::onTracingInstanceStop
);
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
- private final TreeMap<String, IProtoLogGroup> mLogGroups;
+ private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
private final Runnable mCacheUpdater;
- private final Map<LogLevel, Integer> mDefaultLogLevelCounts = new ArrayMap<>();
- private final Map<IProtoLogGroup, Map<LogLevel, Integer>> mLogLevelCounts = new ArrayMap<>();
+ private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
+ private final Map<IProtoLogGroup, int[]> mLogLevelCounts = new ArrayMap<>();
+ private final Map<IProtoLogGroup, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
- private final ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
+ private final Lock mBackgroundServiceLock = new ReentrantLock();
+ private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl(String viewerConfigFilePath,
- TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
+ public PerfettoProtoLogImpl(String viewerConfigFilePath, Runnable cacheUpdater) {
this(() -> {
try {
return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -110,16 +118,19 @@
Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
return null;
}
- }, logGroups, cacheUpdater);
+ }, cacheUpdater);
+ }
+
+ public PerfettoProtoLogImpl() {
+ this(null, null, () -> {});
}
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- TreeMap<String, IProtoLogGroup> logGroups,
Runnable cacheUpdater
) {
this(viewerConfigInputStreamProvider,
- new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups,
+ new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider),
cacheUpdater);
}
@@ -127,7 +138,6 @@
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
ProtoLogViewerConfigReader viewerConfigReader,
- TreeMap<String, IProtoLogGroup> logGroups,
Runnable cacheUpdater
) {
Producer.init(InitArguments.DEFAULTS);
@@ -140,7 +150,6 @@
mDataSource.register(params);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
- this.mLogGroups = logGroups;
this.mCacheUpdater = cacheUpdater;
}
@@ -149,23 +158,70 @@
*/
@VisibleForTesting
@Override
- public void log(LogLevel level, IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable String messageString, Object[] args) {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "log");
+ public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable Object[] args) {
+ log(logLevel, group, new Message(messageHash, paramsMask), args);
+ }
- long tsNanos = SystemClock.elapsedRealtimeNanos();
- try {
- mBackgroundLoggingService.submit(() ->
- logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos));
- if (group.isLogToLogcat()) {
- logToLogcat(group.getTag(), level, messageHash, messageString, args);
+ @Override
+ public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
+ log(logLevel, group, new Message(messageString), args);
+ }
+
+ private void log(LogLevel logLevel, IProtoLogGroup group, Message message,
+ @Nullable Object[] args) {
+ if (isProtoEnabled()) {
+ long tsNanos = SystemClock.elapsedRealtimeNanos();
+ final String stacktrace;
+ if (mCollectStackTraceGroupCounts.getOrDefault(group, 0) > 0) {
+ stacktrace = collectStackTrace();
+ } else {
+ stacktrace = null;
}
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ try {
+ mBackgroundServiceLock.lock();
+ mBackgroundLoggingService.execute(() ->
+ logToProto(logLevel, group, message, args, tsNanos,
+ stacktrace));
+ } finally {
+ mBackgroundServiceLock.unlock();
+ }
+ }
+ if (group.isLogToLogcat()) {
+ logToLogcat(group.getTag(), logLevel, message, args);
}
}
+ private void onTracingFlush() {
+ final ExecutorService loggingService;
+ try {
+ mBackgroundServiceLock.lock();
+ loggingService = mBackgroundLoggingService;
+ mBackgroundLoggingService = Executors.newSingleThreadExecutor();
+ } finally {
+ mBackgroundServiceLock.unlock();
+ }
+
+ try {
+ loggingService.shutdown();
+ boolean finished = loggingService.awaitTermination(10, TimeUnit.SECONDS);
+
+ if (!finished) {
+ Log.e(LOG_TAG, "ProtoLog background tracing service didn't finish gracefully.");
+ }
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Failed to wait for tracing to finish", e);
+ }
+
+ dumpTransitionTraceConfig();
+ }
+
private void dumpTransitionTraceConfig() {
+ if (mViewerConfigInputStreamProvider == null) {
+ // No viewer config available
+ return;
+ }
+
ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
if (pis == null) {
@@ -256,39 +312,44 @@
os.end(outMessagesToken);
}
- private void logToLogcat(String tag, LogLevel level, long messageHash,
- @Nullable String messageString, Object[] args) {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logToLogcat");
- try {
- doLogToLogcat(tag, level, messageHash, messageString, args);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ private void logToLogcat(String tag, LogLevel level, Message message,
+ @Nullable Object[] args) {
+ String messageString = message.getMessage(mViewerConfigReader);
+
+ if (messageString == null) {
+ StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE");
+ if (args != null) {
+ builder.append(" args = (");
+ builder.append(String.join(", ", Arrays.stream(args)
+ .map(it -> {
+ if (it == null) {
+ return "null";
+ } else {
+ return it.toString();
+ }
+ }).toList()));
+ builder.append(")");
+ }
+ messageString = builder.toString();
+ args = new Object[0];
}
+
+ logToLogcat(tag, level, messageString, args);
}
- private void doLogToLogcat(String tag, LogLevel level, long messageHash,
- @androidx.annotation.Nullable String messageString, Object[] args) {
- String message = null;
- if (messageString == null) {
- messageString = mViewerConfigReader.getViewerString(messageHash);
- }
- if (messageString != null) {
- if (args != null) {
- try {
- message = TextUtils.formatSimple(messageString, args);
- } catch (Exception ex) {
- Slog.w(LOG_TAG, "Invalid ProtoLog format string.", ex);
- }
- } else {
- message = messageString;
+ private void logToLogcat(String tag, LogLevel level, String messageString,
+ @Nullable Object[] args) {
+ String message;
+ if (args != null) {
+ try {
+ message = TextUtils.formatSimple(messageString, args);
+ } catch (IllegalArgumentException e) {
+ message = "FORMAT_ERROR \"" + messageString + "\", args=("
+ + String.join(
+ ", ", Arrays.stream(args).map(Object::toString).toList()) + ")";
}
- }
- if (message == null) {
- StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")");
- for (Object o : args) {
- builder.append(" ").append(o);
- }
- message = builder.toString();
+ } else {
+ message = messageString;
}
passToLogcat(tag, level, message);
}
@@ -320,25 +381,11 @@
}
}
- private void logToProto(LogLevel level, String groupName, long messageHash, int paramsMask,
- Object[] args, long tsNanos) {
- if (!isProtoEnabled()) {
- return;
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logToProto");
- try {
- doLogToProto(level, groupName, messageHash, paramsMask, args, tsNanos);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- private void doLogToProto(LogLevel level, String groupName, long messageHash, int paramsMask,
- Object[] args, long tsNanos) {
+ private void logToProto(LogLevel level, IProtoLogGroup logGroup, Message message, Object[] args,
+ long tsNanos, @Nullable String stacktrace) {
mDataSource.trace(ctx -> {
final ProtoLogDataSource.TlsState tlsState = ctx.getCustomTlsState();
- final LogLevel logFrom = tlsState.getLogFromLevel(groupName);
+ final LogLevel logFrom = tlsState.getLogFromLevel(logGroup.name());
if (level.ordinal() < logFrom.ordinal()) {
return;
@@ -350,29 +397,43 @@
// trace processing easier.
int argIndex = 0;
for (Object o : args) {
- int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
+ int type = LogDataType.bitmaskToLogDataType(message.getMessageMask(), argIndex);
if (type == LogDataType.STRING) {
- internStringArg(ctx, o.toString());
+ if (o == null) {
+ internStringArg(ctx, NULL_STRING);
+ } else {
+ internStringArg(ctx, o.toString());
+ }
}
argIndex++;
}
}
int internedStacktrace = 0;
- if (tlsState.getShouldCollectStacktrace(groupName)) {
+ if (tlsState.getShouldCollectStacktrace(logGroup.name())) {
// Intern stackstraces before creating the trace packet for the proto message so
// that the interned stacktrace strings appear before in the trace to make the
// trace processing easier.
- String stacktrace = collectStackTrace();
internedStacktrace = internStacktraceString(ctx, stacktrace);
}
+ boolean needsIncrementalState = false;
+
+ long messageHash = 0;
+ if (message.mMessageHash != null) {
+ messageHash = message.mMessageHash;
+ }
+ if (message.mMessageString != null) {
+ needsIncrementalState = true;
+ messageHash =
+ internProtoMessage(ctx, level, logGroup, message.mMessageString);
+ }
+
final ProtoOutputStream os = ctx.newTracePacket();
os.write(TIMESTAMP, tsNanos);
long token = os.start(PROTOLOG_MESSAGE);
- os.write(MESSAGE_ID, messageHash);
- boolean needsIncrementalState = false;
+ os.write(MESSAGE_ID, messageHash);
if (args != null) {
@@ -381,22 +442,39 @@
ArrayList<Double> doubleParams = new ArrayList<>();
ArrayList<Boolean> booleanParams = new ArrayList<>();
for (Object o : args) {
- int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
+ int type = LogDataType.bitmaskToLogDataType(message.getMessageMask(), argIndex);
try {
switch (type) {
case LogDataType.STRING:
- final int internedStringId = internStringArg(ctx, o.toString());
+ final int internedStringId;
+ if (o == null) {
+ internedStringId = internStringArg(ctx, NULL_STRING);
+ } else {
+ internedStringId = internStringArg(ctx, o.toString());
+ }
os.write(STR_PARAM_IIDS, internedStringId);
needsIncrementalState = true;
break;
case LogDataType.LONG:
- longParams.add(((Number) o).longValue());
+ if (o == null) {
+ longParams.add(0);
+ } else {
+ longParams.add(((Number) o).longValue());
+ }
break;
case LogDataType.DOUBLE:
- doubleParams.add(((Number) o).doubleValue());
+ if (o == null) {
+ doubleParams.add(0d);
+ } else {
+ doubleParams.add(((Number) o).doubleValue());
+ }
break;
case LogDataType.BOOLEAN:
- booleanParams.add((boolean) o);
+ if (o == null) {
+ booleanParams.add(false);
+ } else {
+ booleanParams.add((boolean) o);
+ }
break;
}
} catch (ClassCastException ex) {
@@ -414,7 +492,7 @@
booleanParams.forEach(it -> os.write(BOOLEAN_PARAMS, it ? 1 : 0));
}
- if (tlsState.getShouldCollectStacktrace(groupName)) {
+ if (tlsState.getShouldCollectStacktrace(logGroup.name())) {
os.write(STACKTRACE_IID, internedStacktrace);
}
@@ -427,6 +505,63 @@
});
}
+ private long internProtoMessage(
+ TracingContext<ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState,
+ ProtoLogDataSource.IncrementalState> ctx, LogLevel level,
+ IProtoLogGroup logGroup, String message) {
+ final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();
+
+ if (!incrementalState.clearReported) {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ os.write(SEQUENCE_FLAGS, SEQ_INCREMENTAL_STATE_CLEARED);
+ incrementalState.clearReported = true;
+ }
+
+
+ if (!incrementalState.protologGroupInterningSet.contains(logGroup.getId())) {
+ incrementalState.protologGroupInterningSet.add(logGroup.getId());
+
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long protologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
+ final long groupConfigToken = os.start(GROUPS);
+
+ os.write(ID, logGroup.getId());
+ os.write(NAME, logGroup.name());
+ os.write(TAG, logGroup.getTag());
+
+ os.end(groupConfigToken);
+ os.end(protologViewerConfigToken);
+ }
+
+ final Long messageHash = hash(level, logGroup.name(), message);
+ if (!incrementalState.protologMessageInterningSet.contains(messageHash)) {
+ incrementalState.protologMessageInterningSet.add(messageHash);
+
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long protologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
+ final long messageConfigToken = os.start(MESSAGES);
+
+ os.write(MessageData.MESSAGE_ID, messageHash);
+ os.write(MESSAGE, message);
+ os.write(LEVEL, level.ordinal());
+ os.write(GROUP_ID, logGroup.getId());
+
+ os.end(messageConfigToken);
+ os.end(protologViewerConfigToken);
+ }
+
+ return messageHash;
+ }
+
+ private Long hash(
+ LogLevel logLevel,
+ String logGroup,
+ String messageString
+ ) {
+ final String fullStringIdentifier = messageString + logLevel + logGroup;
+ return UUID.nameUUIDFromBytes(fullStringIdentifier.getBytes()).getMostSignificantBits();
+ }
+
private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 12;
private String collectStackTrace() {
@@ -466,7 +601,7 @@
ProtoLogDataSource.IncrementalState> ctx,
Map<String, Integer> internMap,
long fieldId,
- String string
+ @NonNull String string
) {
final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();
@@ -523,25 +658,17 @@
@Override
public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
- return group.isLogToLogcat() || getLogFromLevel(group).ordinal() <= level.ordinal();
+ final int[] groupLevelCount = mLogLevelCounts.get(group);
+ return (groupLevelCount == null && mDefaultLogLevelCounts[level.ordinal()] > 0)
+ || (groupLevelCount != null && groupLevelCount[level.ordinal()] > 0)
+ || group.isLogToLogcat();
}
- private LogLevel getLogFromLevel(IProtoLogGroup group) {
- if (mLogLevelCounts.containsKey(group)) {
- for (LogLevel logLevel : LogLevel.values()) {
- if (mLogLevelCounts.get(group).getOrDefault(logLevel, 0) > 0) {
- return logLevel;
- }
- }
- } else {
- for (LogLevel logLevel : LogLevel.values()) {
- if (mDefaultLogLevelCounts.getOrDefault(logLevel, 0) > 0) {
- return logLevel;
- }
- }
+ @Override
+ public void registerGroups(IProtoLogGroup... protoLogGroups) {
+ for (IProtoLogGroup protoLogGroup : protoLogGroups) {
+ mLogGroups.put(protoLogGroup.name(), protoLogGroup);
}
-
- return LogLevel.WTF;
}
/**
@@ -620,36 +747,51 @@
}
private synchronized void onTracingInstanceStart(ProtoLogDataSource.ProtoLogConfig config) {
- this.mTracingInstances.incrementAndGet();
-
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
- mDefaultLogLevelCounts.put(defaultLogFrom,
- mDefaultLogLevelCounts.getOrDefault(defaultLogFrom, 0) + 1);
+ for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
+ mDefaultLogLevelCounts[i]++;
+ }
final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
for (String overriddenGroupTag : overriddenGroupTags) {
IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
- mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
- final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
+ if (group == null) {
+ throw new IllegalArgumentException("Trying to set config for \""
+ + overriddenGroupTag + "\" that isn't registered");
+ }
+
+ mLogLevelCounts.putIfAbsent(group, new int[LogLevel.values().length]);
+ final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
- logLevelsCountsForGroup.put(logFromLevel,
- logLevelsCountsForGroup.getOrDefault(logFromLevel, 0) + 1);
+ for (int i = logFromLevel.ordinal(); i < LogLevel.values().length; i++) {
+ logLevelsCountsForGroup[i]++;
+ }
+
+ if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
+ mCollectStackTraceGroupCounts.put(group,
+ mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
+ }
+
+ if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
+ mCollectStackTraceGroupCounts.put(group,
+ mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
+ }
}
mCacheUpdater.run();
+
+ this.mTracingInstances.incrementAndGet();
}
private synchronized void onTracingInstanceStop(ProtoLogDataSource.ProtoLogConfig config) {
this.mTracingInstances.decrementAndGet();
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
- mDefaultLogLevelCounts.put(defaultLogFrom,
- mDefaultLogLevelCounts.get(defaultLogFrom) - 1);
- if (mDefaultLogLevelCounts.get(defaultLogFrom) <= 0) {
- mDefaultLogLevelCounts.remove(defaultLogFrom);
+ for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
+ mDefaultLogLevelCounts[i]--;
}
final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
@@ -657,18 +799,24 @@
for (String overriddenGroupTag : overriddenGroupTags) {
IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
- mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
- final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
+ final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
- logLevelsCountsForGroup.put(logFromLevel,
- logLevelsCountsForGroup.get(logFromLevel) - 1);
- if (logLevelsCountsForGroup.get(logFromLevel) <= 0) {
- logLevelsCountsForGroup.remove(logFromLevel);
+ for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
+ logLevelsCountsForGroup[i]--;
}
- if (logLevelsCountsForGroup.isEmpty()) {
+ if (Arrays.stream(logLevelsCountsForGroup).allMatch(it -> it == 0)) {
mLogLevelCounts.remove(group);
}
+
+ if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
+ mCollectStackTraceGroupCounts.put(group,
+ mCollectStackTraceGroupCounts.get(group) - 1);
+
+ if (mCollectStackTraceGroupCounts.get(group) == 0) {
+ mCollectStackTraceGroupCounts.remove(group);
+ }
+ }
}
mCacheUpdater.run();
@@ -681,5 +829,36 @@
pw.flush();
}
}
+
+ private static class Message {
+ private final Long mMessageHash;
+ private final Integer mMessageMask;
+ private final String mMessageString;
+
+ private Message(Long messageHash, int messageMask) {
+ this.mMessageHash = messageHash;
+ this.mMessageMask = messageMask;
+ this.mMessageString = null;
+ }
+
+ private Message(String messageString) {
+ this.mMessageHash = null;
+ final List<Integer> argTypes = LogDataType.parseFormatString(messageString);
+ this.mMessageMask = LogDataType.logDataTypesToBitMask(argTypes);
+ this.mMessageString = messageString;
+ }
+
+ private int getMessageMask() {
+ return mMessageMask;
+ }
+
+ private String getMessage(ProtoLogViewerConfigReader viewerConfigReader) {
+ if (mMessageString != null) {
+ return mMessageString;
+ }
+
+ return viewerConfigReader.getViewerString(mMessageHash);
+ }
+ }
}
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 0118c05..87678e5 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -44,21 +44,23 @@
// LINT.ThenChange(frameworks/base/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt)
// Needs to be set directly otherwise the protologtool tries to transform the method call
+ @Deprecated
public static boolean REQUIRE_PROTOLOGTOOL = true;
+ private static IProtoLog sProtoLogInstance;
+
/**
* DEBUG level log.
*
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
+ *
+ * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
+ * executed. Check generated code for actual call.
*/
public static void d(IProtoLogGroup group, String messageString, Object... args) {
- // Stub, replaced by the ProtoLogTool.
- if (REQUIRE_PROTOLOGTOOL) {
- throw new UnsupportedOperationException(
- "ProtoLog calls MUST be processed with ProtoLogTool");
- }
+ logStringMessage(LogLevel.DEBUG, group, messageString, args);
}
/**
@@ -67,13 +69,12 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
+ *
+ * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
+ * executed. Check generated code for actual call.
*/
public static void v(IProtoLogGroup group, String messageString, Object... args) {
- // Stub, replaced by the ProtoLogTool.
- if (REQUIRE_PROTOLOGTOOL) {
- throw new UnsupportedOperationException(
- "ProtoLog calls MUST be processed with ProtoLogTool");
- }
+ logStringMessage(LogLevel.VERBOSE, group, messageString, args);
}
/**
@@ -82,13 +83,12 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
+ *
+ * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
+ * executed. Check generated code for actual call.
*/
public static void i(IProtoLogGroup group, String messageString, Object... args) {
- // Stub, replaced by the ProtoLogTool.
- if (REQUIRE_PROTOLOGTOOL) {
- throw new UnsupportedOperationException(
- "ProtoLog calls MUST be processed with ProtoLogTool");
- }
+ logStringMessage(LogLevel.INFO, group, messageString, args);
}
/**
@@ -97,13 +97,12 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
+ *
+ * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
+ * executed. Check generated code for actual call.
*/
public static void w(IProtoLogGroup group, String messageString, Object... args) {
- // Stub, replaced by the ProtoLogTool.
- if (REQUIRE_PROTOLOGTOOL) {
- throw new UnsupportedOperationException(
- "ProtoLog calls MUST be processed with ProtoLogTool");
- }
+ logStringMessage(LogLevel.WARN, group, messageString, args);
}
/**
@@ -112,13 +111,12 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
+ *
+ * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
+ * executed. Check generated code for actual call.
*/
public static void e(IProtoLogGroup group, String messageString, Object... args) {
- // Stub, replaced by the ProtoLogTool.
- if (REQUIRE_PROTOLOGTOOL) {
- throw new UnsupportedOperationException(
- "ProtoLog calls MUST be processed with ProtoLogTool");
- }
+ logStringMessage(LogLevel.ERROR, group, messageString, args);
}
/**
@@ -127,13 +125,12 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
+ *
+ * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
+ * executed. Check generated code for actual call.
*/
public static void wtf(IProtoLogGroup group, String messageString, Object... args) {
- // Stub, replaced by the ProtoLogTool.
- if (REQUIRE_PROTOLOGTOOL) {
- throw new UnsupportedOperationException(
- "ProtoLog calls MUST be processed with ProtoLogTool");
- }
+ logStringMessage(LogLevel.WTF, group, messageString, args);
}
/**
@@ -142,11 +139,7 @@
* @return true iff this is being logged.
*/
public static boolean isEnabled(IProtoLogGroup group, LogLevel level) {
- if (REQUIRE_PROTOLOGTOOL) {
- throw new UnsupportedOperationException(
- "ProtoLog calls MUST be processed with ProtoLogTool");
- }
- return false;
+ return sProtoLogInstance.isEnabled(group, level);
}
/**
@@ -154,10 +147,36 @@
* @return A singleton instance of ProtoLog.
*/
public static IProtoLog getSingleInstance() {
- if (REQUIRE_PROTOLOGTOOL) {
- throw new UnsupportedOperationException(
- "ProtoLog calls MUST be processed with ProtoLogTool");
+ return sProtoLogInstance;
+ }
+
+ /**
+ * Registers available protolog groups. A group must be registered before it can be used.
+ * @param protoLogGroups The groups to register for use in protolog.
+ */
+ public static void registerGroups(IProtoLogGroup... protoLogGroups) {
+ sProtoLogInstance.registerGroups(protoLogGroups);
+ }
+
+ private static void logStringMessage(LogLevel logLevel, IProtoLogGroup group,
+ String stringMessage, Object... args) {
+ if (sProtoLogInstance == null) {
+ throw new IllegalStateException(
+ "Trying to use ProtoLog before it is initialized in this process.");
}
- return null;
+
+ if (sProtoLogInstance.isEnabled(group, logLevel)) {
+ sProtoLogInstance.log(logLevel, group, stringMessage, args);
+ }
+ }
+
+ static {
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ sProtoLogInstance = new PerfettoProtoLogImpl();
+ } else {
+ // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
+ // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
+ sProtoLogInstance = new LogcatOnlyProtoLogImpl();
+ }
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 6ab79b9..84f3237 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -40,6 +40,7 @@
import java.io.IOException;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
@@ -138,6 +139,8 @@
}
public static class IncrementalState {
+ public final Set<Integer> protologGroupInterningSet = new HashSet<>();
+ public final Set<Long> protologMessageInterningSet = new HashSet<>();
public final Map<String, Integer> argumentInterningMap = new HashMap<>();
public final Map<String, Integer> stacktraceInterningMap = new HashMap<>();
public boolean clearReported = false;
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 6d142af..3082295 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -54,48 +54,33 @@
private static Runnable sCacheUpdater;
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance()
- .log(LogLevel.DEBUG, group, messageHash, paramsMask, messageString, args);
+ public static void d(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
+ getSingleInstance().log(LogLevel.DEBUG, group, messageHash, paramsMask, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void v(IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, messageString,
- args);
+ public static void v(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
+ getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void i(IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, messageString, args);
+ public static void i(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
+ getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void w(IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, messageString, args);
+ public static void w(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
+ getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void e(IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance()
- .log(LogLevel.ERROR, group, messageHash, paramsMask, messageString, args);
+ public static void e(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
+ getSingleInstance().log(LogLevel.ERROR, group, messageHash, paramsMask, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void wtf(IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
+ public static void wtf(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
+ getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, args);
}
/**
@@ -107,18 +92,27 @@
}
/**
+ * Registers available protolog groups. A group must be registered before it can be used.
+ * @param protoLogGroups The groups to register for use in protolog.
+ */
+ public static void registerGroups(IProtoLogGroup... protoLogGroups) {
+ getSingleInstance().registerGroups(protoLogGroups);
+ }
+
+ /**
* Returns the single instance of the ProtoLogImpl singleton class.
*/
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sServiceInstance = new PerfettoProtoLogImpl(
- sViewerConfigPath, sLogGroups, sCacheUpdater);
+ sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
} else {
sServiceInstance = new LegacyProtoLogImpl(
- sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups, sCacheUpdater);
+ sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
}
+ IProtoLogGroup[] groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
+ sServiceInstance.registerGroups(groups);
sCacheUpdater.run();
}
return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index f72d9f7..f5695ac 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -27,11 +27,19 @@
* @param group The group this message belongs to.
* @param messageHash The hash of the message.
* @param paramsMask The parameters mask of the message.
- * @param messageString The message string.
* @param args The arguments of the message.
*/
void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
- String messageString, Object[] args);
+ Object[] args);
+
+ /**
+ * Log a ProtoLog message
+ * @param logLevel Log level of the proto message.
+ * @param group The group this message belongs to.
+ * @param messageString The message string.
+ * @param args The arguments of the message.
+ */
+ void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args);
/**
* Check if ProtoLog is tracing.
@@ -60,4 +68,10 @@
* @return If we need to log this group and level to either ProtoLog or Logcat.
*/
boolean isEnabled(IProtoLogGroup group, LogLevel level);
+
+ /**
+ * Registers available protolog groups. A group must be registered before it can be used.
+ * @param protoLogGroups The groups to register for use in protolog.
+ */
+ void registerGroups(IProtoLogGroup... protoLogGroups);
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4f0a836..dc3d935 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -533,6 +533,9 @@
<!-- If this is true, key chords can be used to take a screenshot on the device. -->
<bool name="config_enableScreenshotChord">true</bool>
+ <!-- If this is true, accessibility events on notifications are sent. -->
+ <bool name="config_enableNotificationAccessibilityEvents">true</bool>
+
<!-- If this is true, allow wake from theater mode when plugged in or unplugged. -->
<bool name="config_allowTheaterModeWakeFromUnplug">false</bool>
<!-- If this is true, allow wake from theater mode from gesture. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 419a615..d25f59d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2006,6 +2006,7 @@
<java-symbol type="array" name="config_notificationFallbackVibeWaveform" />
<java-symbol type="bool" name="config_enableServerNotificationEffectsForAutomotive" />
<java-symbol type="bool" name="config_useAttentionLight" />
+ <java-symbol type="bool" name="config_enableNotificationAccessibilityEvents" />
<java-symbol type="bool" name="config_adaptive_sleep_available" />
<java-symbol type="bool" name="config_camera_autorotate"/>
<java-symbol type="bool" name="config_animateScreenLights" />
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index fc63657..f67ad3f 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -33,14 +33,14 @@
There must be one of these for each display, labeled:
ambient.on.display0, ambient.on.display1, etc...
- Each display suffix number should match it's ordinal in its display device config.
+ Each display suffix number should match its ordinal in its display device config.
-->
<item name="ambient.on.display0">0.1</item> <!-- ~100mA -->
<!-- Average battery current draw of display0 while on without backlight.
There must be one of these for each display, labeled:
screen.on.display0, screen.on.display1, etc...
- Each display suffix number should match it's ordinal in its display device config.
+ Each display suffix number should match its ordinal in its display device config.
-->
<item name="screen.on.display0">0.1</item> <!-- ~100mA -->
<!-- Average battery current draw of the backlight at full brightness.
@@ -50,7 +50,7 @@
There must be one of these for each display, labeled:
screen.full.display0, screen.full.display1, etc...
- Each display suffix number should match it's ordinal in its display device config.
+ Each display suffix number should match its ordinal in its display device config.
-->
<item name="screen.full.display0">0.1</item> <!-- ~100mA -->
diff --git a/core/tests/coretests/src/android/util/SequenceUtilsTest.java b/core/tests/coretests/src/android/util/SequenceUtilsTest.java
index 020520d..6ca1751 100644
--- a/core/tests/coretests/src/android/util/SequenceUtilsTest.java
+++ b/core/tests/coretests/src/android/util/SequenceUtilsTest.java
@@ -19,7 +19,7 @@
import static android.util.SequenceUtils.getInitSeq;
import static android.util.SequenceUtils.getNextSeq;
-import static android.util.SequenceUtils.isIncomingSeqNewer;
+import static android.util.SequenceUtils.isIncomingSeqStale;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -60,30 +60,35 @@
}
@Test
- public void testIsIncomingSeqNewer() {
- assertTrue(isIncomingSeqNewer(getInitSeq() + 1, getInitSeq() + 10));
- assertFalse(isIncomingSeqNewer(getInitSeq() + 10, getInitSeq() + 1));
- assertTrue(isIncomingSeqNewer(-100, 100));
- assertFalse(isIncomingSeqNewer(100, -100));
- assertTrue(isIncomingSeqNewer(1, 2));
- assertFalse(isIncomingSeqNewer(2, 1));
+ public void testIsIncomingSeqStale() {
+ assertFalse(isIncomingSeqStale(getInitSeq() + 1, getInitSeq() + 10));
+ assertTrue(isIncomingSeqStale(getInitSeq() + 10, getInitSeq() + 1));
+ assertFalse(isIncomingSeqStale(-100, 100));
+ assertTrue(isIncomingSeqStale(100, -100));
+ assertFalse(isIncomingSeqStale(1, 2));
+ assertTrue(isIncomingSeqStale(2, 1));
// Possible incoming seq are all newer than the initial seq.
- assertTrue(isIncomingSeqNewer(getInitSeq(), getInitSeq() + 1));
- assertTrue(isIncomingSeqNewer(getInitSeq(), -100));
- assertTrue(isIncomingSeqNewer(getInitSeq(), 0));
- assertTrue(isIncomingSeqNewer(getInitSeq(), 100));
- assertTrue(isIncomingSeqNewer(getInitSeq(), Integer.MAX_VALUE));
- assertTrue(isIncomingSeqNewer(getInitSeq(), getNextSeq(Integer.MAX_VALUE)));
+ assertFalse(isIncomingSeqStale(getInitSeq(), getInitSeq()));
+ assertFalse(isIncomingSeqStale(getInitSeq(), getInitSeq() + 1));
+ assertFalse(isIncomingSeqStale(getInitSeq(), -100));
+ assertFalse(isIncomingSeqStale(getInitSeq(), 0));
+ assertFalse(isIncomingSeqStale(getInitSeq(), 100));
+ assertFalse(isIncomingSeqStale(getInitSeq(), Integer.MAX_VALUE));
+ assertFalse(isIncomingSeqStale(getInitSeq(), getNextSeq(Integer.MAX_VALUE)));
// False for the same seq.
- assertFalse(isIncomingSeqNewer(getInitSeq(), getInitSeq()));
- assertFalse(isIncomingSeqNewer(100, 100));
- assertFalse(isIncomingSeqNewer(Integer.MAX_VALUE, Integer.MAX_VALUE));
+ assertFalse(isIncomingSeqStale(100, 100));
+ assertFalse(isIncomingSeqStale(Integer.MAX_VALUE, Integer.MAX_VALUE));
- // True when there is a large jump (overflow).
- assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getInitSeq() + 1));
- assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getInitSeq() + 100));
- assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getNextSeq(Integer.MAX_VALUE)));
+ // False when there is a large jump (overflow).
+ assertFalse(isIncomingSeqStale(Integer.MAX_VALUE, getInitSeq() + 1));
+ assertFalse(isIncomingSeqStale(Integer.MAX_VALUE, getInitSeq() + 100));
+ assertFalse(isIncomingSeqStale(Integer.MAX_VALUE, getNextSeq(Integer.MAX_VALUE)));
+
+ // True when the large jump is opposite (curSeq is newer).
+ assertTrue(isIncomingSeqStale(getInitSeq() + 1, Integer.MAX_VALUE));
+ assertTrue(isIncomingSeqStale(getInitSeq() + 100, Integer.MAX_VALUE));
+ assertTrue(isIncomingSeqStale(getNextSeq(Integer.MAX_VALUE), Integer.MAX_VALUE));
}
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 9337bf6..033ac7c 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.util.SequenceUtils.getInitSeq;
import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -1555,9 +1556,9 @@
final InsetsState state0 = new InsetsState();
final InsetsState state1 = new InsetsState();
state0.setDisplayFrame(new Rect(0, 0, 500, 1000));
- state0.setSeq(10000);
+ state0.setSeq(getInitSeq() + 10000);
state1.setDisplayFrame(new Rect(0, 0, 1500, 2000));
- state1.setSeq(10001);
+ state1.setSeq(getInitSeq() + 10001);
final InsetsSourceControl.Array array = new InsetsSourceControl.Array();
sInstrumentation.runOnMainSync(() -> {
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1e68241..dbcad8a 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -190,6 +190,15 @@
],
}
+java_library {
+ name: "WindowManager-Shell-shared-desktopMode",
+
+ srcs: [
+ "shared/**/desktopmode/*.java",
+ "shared/**/desktopmode/*.kt",
+ ],
+}
+
android_library {
name: "WindowManager-Shell",
srcs: [
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml
new file mode 100644
index 0000000..7d912a2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+ <path android:fillColor="@android:color/black" android:pathData="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,440Q80,407 103.5,383.5Q127,360 160,360L240,360L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,520Q880,553 856.5,576.5Q833,600 800,600L720,600L720,800Q720,833 696.5,856.5Q673,880 640,880L160,880ZM160,800L640,800Q640,800 640,800Q640,800 640,800L640,520L160,520L160,800Q160,800 160,800Q160,800 160,800ZM720,520L800,520Q800,520 800,520Q800,520 800,520L800,240L320,240L320,360L640,360Q673,360 696.5,383.5Q720,407 720,440L720,520Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index d5724cc..419d5c0 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -135,5 +135,24 @@
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
</LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/open_in_browser_pill"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height"
+ android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
+ android:layout_marginStart="1dp"
+ android:orientation="vertical"
+ android:elevation="1dp"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background">
+
+ <Button
+ android:id="@+id/open_in_browser_button"
+ android:contentDescription="@string/open_in_browser_text"
+ android:text="@string/open_in_browser_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+ </LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 595d346..d143263 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -507,8 +507,11 @@
<!-- The height of the handle menu's "More Actions" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_more_actions_pill_height">52dp</dimen>
+ <!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
+
<!-- The height of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_height">328dp</dimen>
+ <dimen name="desktop_mode_handle_menu_height">380dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 4784674..4e7cfb6 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -280,6 +280,8 @@
<string name="select_text">Select</string>
<!-- Accessibility text for the handle menu screenshot button [CHAR LIMIT=NONE] -->
<string name="screenshot_text">Screenshot</string>
+ <!-- Accessibility text for the handle menu open in browser button [CHAR LIMIT=NONE] -->
+ <string name="open_in_browser_text">Open in browser</string>
<!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 8c2cfd1e..f0d80a0 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -84,11 +84,12 @@
// Read Setting Global if System Property is not present (just after reboot)
// or not valid (user manually changed the value)
val overrideFromSettingsGlobal =
- Settings.Global.getInt(
+ convertToToggleOverrideWithFallback(
+ Settings.Global.getInt(
context.contentResolver,
Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
- ToggleOverride.OVERRIDE_UNSET.setting)
- .convertToToggleOverrideWithFallback(ToggleOverride.OVERRIDE_UNSET)
+ ToggleOverride.OVERRIDE_UNSET.setting),
+ ToggleOverride.OVERRIDE_UNSET)
// Initialize System Property
System.setProperty(
SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString())
@@ -97,7 +98,6 @@
}
}
- // TODO(b/348193756): Share ToggleOverride enum with Settings 'DesktopModePreferenceController'
/**
* Override state of desktop mode developer option toggle.
*
@@ -113,8 +113,6 @@
OVERRIDE_ON(1)
}
- private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting }
-
private fun String?.convertToToggleOverride(): ToggleOverride? {
val intValue = this?.toIntOrNull() ?: return null
return settingToToggleOverrideMap[intValue]
@@ -124,16 +122,6 @@
}
}
- private fun Int.convertToToggleOverrideWithFallback(
- fallbackOverride: ToggleOverride
- ): ToggleOverride {
- return settingToToggleOverrideMap[this]
- ?: run {
- Log.w(TAG, "Unknown toggleOverride int $this")
- fallbackOverride
- }
- }
-
companion object {
private const val TAG = "DesktopModeFlags"
@@ -148,5 +136,19 @@
* be refreshed only on reboots as overridden state takes effect on reboots.
*/
private var cachedToggleOverride: ToggleOverride? = null
+
+ private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting }
+
+ @JvmStatic
+ fun convertToToggleOverrideWithFallback(
+ overrideInt: Int,
+ fallbackOverride: ToggleOverride
+ ): ToggleOverride {
+ return settingToToggleOverrideMap[overrideInt]
+ ?: run {
+ Log.w(TAG, "Unknown toggleOverride int $overrideInt")
+ fallbackOverride
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index d41600b..f014e55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -699,6 +699,22 @@
}
}
+ /**
+ * Shows/hides the given task surface. Not for general use as changing the task visibility may
+ * conflict with other Transitions. This is currently ONLY used to temporarily hide a task
+ * while a drag is in session.
+ */
+ public void setTaskSurfaceVisibility(int taskId, boolean visible) {
+ synchronized (mLock) {
+ final TaskAppearedInfo info = mTasks.get(taskId);
+ if (info != null) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setVisibility(info.getLeash(), visible);
+ t.apply();
+ }
+ }
+ }
+
private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
TaskListener oldListener, TaskListener newListener) {
if (oldListener == newListener) return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
index a124f95..c93c11e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
@@ -20,10 +20,10 @@
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Bundle
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.Flags
import com.android.wm.shell.R
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES
-import com.android.wm.shell.util.KtProtoLog
/** Activity to create a shortcut to open bubbles */
class CreateBubbleShortcutActivity : Activity() {
@@ -31,7 +31,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Flags.enableRetrievableBubbles()) {
- KtProtoLog.d(WM_SHELL_BUBBLES, "Creating a shortcut for bubbles")
+ ProtoLog.d(WM_SHELL_BUBBLES, "Creating a shortcut for bubbles")
createShortcut()
}
finish()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
index ae7940c..e578e9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
@@ -21,9 +21,9 @@
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.Flags
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES
-import com.android.wm.shell.util.KtProtoLog
/** Activity that sends a broadcast to open bubbles */
class ShowBubblesActivity : Activity() {
@@ -37,7 +37,7 @@
// Set the package as the receiver is not exported
`package` = packageName
}
- KtProtoLog.v(WM_SHELL_BUBBLES, "Sending broadcast to show bubbles")
+ ProtoLog.v(WM_SHELL_BUBBLES, "Sending broadcast to show bubbles")
sendBroadcast(intent)
}
finish()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
index 81592c3..e92b0b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
@@ -17,8 +17,8 @@
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
-import com.android.wm.shell.util.KtProtoLog
/**
* Controller to manage behavior of activities launched with
@@ -30,7 +30,7 @@
var launchAdjacentEnabled: Boolean = true
set(value) {
if (field != value) {
- KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
+ ProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
field = value
container?.let { c ->
if (value) {
@@ -52,7 +52,7 @@
* @see WindowContainerTransaction.setLaunchAdjacentFlagRoot
*/
fun setLaunchAdjacentRoot(container: WindowContainerToken) {
- KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
+ ProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
this.container = container
if (launchAdjacentEnabled) {
enableContainer(container)
@@ -67,7 +67,7 @@
* @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot
*/
fun clearLaunchAdjacentRoot() {
- KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
+ ProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
container?.let {
disableContainer(it)
container = null
@@ -75,14 +75,14 @@
}
private fun enableContainer(container: WindowContainerToken) {
- KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
+ ProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
val wct = WindowContainerTransaction()
wct.setLaunchAdjacentFlagRoot(container)
syncQueue.queue(wct)
}
private fun disableContainer(container: WindowContainerToken) {
- KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
+ ProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
val wct = WindowContainerTransaction()
wct.clearLaunchAdjacentFlagRoot(container)
syncQueue.queue(wct)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
index 9e8dfb5..a6be640 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
@@ -23,9 +23,9 @@
import android.os.UserHandle
import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL
-import com.android.wm.shell.util.KtProtoLog
import java.util.Arrays
/**
@@ -52,7 +52,7 @@
val packageName = componentName.packageName
for (pkg in staticAppsSupportingMultiInstance) {
if (pkg == packageName) {
- KtProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
+ ProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
packageName)
return true
}
@@ -70,10 +70,10 @@
// If the above call doesn't throw a NameNotFoundException, then the activity property
// should override the application property value
if (activityProp.isBoolean) {
- KtProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName)
+ ProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName)
return activityProp.boolean
} else {
- KtProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d",
+ ProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d",
PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, activityProp.type)
}
} catch (nnfe: PackageManager.NameNotFoundException) {
@@ -85,10 +85,10 @@
val appProp = packageManager.getProperty(
PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName)
if (appProp.isBoolean) {
- KtProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
+ ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
return appProp.boolean
} else {
- KtProtoLog.w(WM_SHELL,
+ ProtoLog.w(WM_SHELL,
"Warning: property=%s for application=%s has non-bool type=%d",
PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.type)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 3ad60e7..1bc1795 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -492,6 +492,11 @@
return mHideHandle;
}
+ /** Returns true if the divider is currently being physically controlled by the user. */
+ boolean isMoving() {
+ return mMoving;
+ }
+
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index bdef4f4..51f9de8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -652,9 +652,18 @@
.ofInt(from, to)
.setDuration(duration);
mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+
+ // If the divider is being physically controlled by the user, we use a cool parallax effect
+ // on the task windows. So if this "snap" animation is an extension of a user-controlled
+ // movement, we pass in true here to continue the parallax effect smoothly.
+ boolean isBeingMovedByUser = mSplitWindowManager.getDividerView() != null
+ && mSplitWindowManager.getDividerView().isMoving();
+
mDividerFlingAnimator.addUpdateListener(
animation -> updateDividerBounds(
- (int) animation.getAnimatedValue(), false /* shouldUseParallaxEffect */)
+ (int) animation.getAnimatedValue(),
+ isBeingMovedByUser /* shouldUseParallaxEffect */
+ )
);
mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 5d121c2..46c1a43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -37,7 +37,6 @@
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
-import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
@@ -192,7 +191,7 @@
mDividerView.setInteractive(interactive, hideHandle, from);
}
- View getDividerView() {
+ DividerView getDividerView() {
return mDividerView;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index aa499d9..eeceaa9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -604,11 +604,12 @@
Context context,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
Transitions transitions,
+ ShellTaskOrganizer shellTaskOrganizer,
ShellInit shellInit
) {
return desktopModeTaskRepository.flatMap(repository ->
Optional.of(new DesktopTasksTransitionObserver(
- context, repository, transitions, shellInit))
+ context, repository, transitions, shellTaskOrganizer, shellInit))
);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index fbc11c1..400882a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.desktopmode
+import com.android.internal.protolog.ProtoLog
import com.android.internal.util.FrameworkStatsLog
import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.util.KtProtoLog
/** Event logger for logging desktop mode session events */
class DesktopModeEventLogger {
@@ -27,7 +27,7 @@
* entering desktop mode
*/
fun logSessionEnter(sessionId: Int, enterReason: EnterReason) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session enter, session: %s reason: %s",
sessionId,
@@ -47,7 +47,7 @@
* exiting desktop mode
*/
fun logSessionExit(sessionId: Int, exitReason: ExitReason) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session exit, session: %s reason: %s",
sessionId,
@@ -67,7 +67,7 @@
* session id [sessionId]
*/
fun logTaskAdded(sessionId: Int, taskUpdate: TaskUpdate) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task added, session: %s taskId: %s",
sessionId,
@@ -99,7 +99,7 @@
* session id [sessionId]
*/
fun logTaskRemoved(sessionId: Int, taskUpdate: TaskUpdate) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task remove, session: %s taskId: %s",
sessionId,
@@ -131,7 +131,7 @@
* having session id [sessionId]
*/
fun logTaskInfoChanged(sessionId: Int, taskUpdate: TaskUpdate) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task info changed, session: %s taskId: %s",
sessionId,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 275f725d..066b5ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -50,7 +50,6 @@
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.util.KtProtoLog
/**
* A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
@@ -106,7 +105,7 @@
) {
// this was a new recents animation
if (info.isExitToRecentsTransition() && tasksSavedForRecents.isEmpty()) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Recents animation running, saving tasks for later"
)
@@ -132,7 +131,7 @@
info.flags == 0 &&
tasksSavedForRecents.isNotEmpty()
) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Canceled recents animation, restoring tasks"
)
@@ -202,7 +201,7 @@
}
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: taskInfo map after processing changes %s",
postTransitionFreeformTasks.size()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index df79b15..ca05864 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -26,8 +26,8 @@
import androidx.core.util.forEach
import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.util.KtProtoLog
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -142,7 +142,7 @@
val added = displayData.getOrCreate(displayId).activeTasks.add(taskId)
if (added) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: add active task=%d displayId=%d",
taskId,
@@ -167,7 +167,7 @@
}
}
if (result) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
}
return result
}
@@ -180,7 +180,7 @@
fun addClosingTask(displayId: Int, taskId: Int): Boolean {
val added = displayData.getOrCreate(displayId).closingTasks.add(taskId)
if (added) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: added closing task=%d displayId=%d",
taskId,
@@ -203,7 +203,7 @@
}
}
if (removed) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId)
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId)
}
return removed
}
@@ -316,14 +316,14 @@
// Check if count changed
if (prevCount != newCount) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d",
taskId,
visible,
displayId
)
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
prevCount,
@@ -341,7 +341,7 @@
/** Get number of tasks that are marked as visible on given [displayId] */
fun getVisibleTaskCount(displayId: Int): Int {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount= %d",
displayData[displayId]?.visibleTasks?.size ?: 0
@@ -353,7 +353,7 @@
// TODO(b/342417921): Identify if there is additional checks needed to move tasks for
// multi-display scenarios.
fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: add or move task to top: display=%d, taskId=%d",
displayId,
@@ -365,7 +365,7 @@
/** Mark a Task as minimized. */
fun minimizeTask(displayId: Int, taskId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeTaskRepository: minimize Task: display=%d, task=%d",
displayId,
@@ -376,7 +376,7 @@
/** Mark a Task as non-minimized. */
fun unminimizeTask(displayId: Int, taskId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeTaskRepository: unminimize Task: display=%d, task=%d",
displayId,
@@ -387,7 +387,7 @@
/** Remove the task from the ordered list. */
fun removeFreeformTask(displayId: Int, taskId: Int) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: remove freeform task from ordered list: display=%d, taskId=%d",
displayId,
@@ -395,7 +395,7 @@
)
displayData[displayId]?.freeformTasksInZOrder?.remove(taskId)
boundsBeforeMaximizeByTaskId.remove(taskId)
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: remaining freeform tasks: %s",
displayData[displayId]?.freeformTasksInZOrder?.toDumpString() ?: ""
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 985901d..18157d6 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
@@ -50,6 +50,7 @@
import androidx.annotation.BinderThread
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -88,7 +89,6 @@
import com.android.wm.shell.sysui.ShellSharedConstants
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.util.KtProtoLog
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
@@ -186,7 +186,7 @@
}
private fun onInit() {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, this)
shellController.addExternalInterface(
@@ -200,7 +200,7 @@
recentsTransitionHandler.addTransitionStateListener(
object : RecentsTransitionStateListener {
override fun onAnimationStateChanged(running: Boolean) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: recents animation state changed running=%b",
running
@@ -231,7 +231,7 @@
/** Show all tasks, that are part of the desktop, on top of launcher */
fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
val wct = WindowContainerTransaction()
bringDesktopAppsToFront(displayId, wct)
@@ -282,7 +282,7 @@
moveToDesktop(allFocusedTasks[0].taskId, transitionSource = transitionSource)
}
else -> {
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: Cannot enter desktop, expected less " +
"than 3 focused tasks but found %d",
@@ -312,7 +312,7 @@
transitionSource: DesktopModeTransitionSource,
): Boolean {
recentTasksController?.findTaskInBackground(taskId)?.let {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d",
taskId
@@ -346,14 +346,14 @@
) {
if (Flags.enableDesktopWindowingModalsPolicy()
&& isTopActivityExemptFromDesktopWindowing(context, task)) {
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: Cannot enter desktop, " +
"ineligible top activity found."
)
return
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToDesktop taskId=%d",
task.taskId
@@ -380,7 +380,7 @@
taskInfo: RunningTaskInfo,
dragToDesktopValueAnimator: MoveToDesktopAnimator,
) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: startDragToDesktop taskId=%d",
taskInfo.taskId
@@ -396,7 +396,7 @@
* [startDragToDesktop].
*/
private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: finalizeDragToDesktop taskId=%d",
taskInfo.taskId
@@ -440,7 +440,7 @@
}
if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) {
// Could happen if the task hasn't been removed from closing list after it disappeared
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: the task with taskId=%d is already closing!",
taskId
@@ -464,7 +464,7 @@
/** Move a desktop app to split screen. */
fun moveToSplit(task: RunningTaskInfo) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToSplit taskId=%d",
task.taskId
@@ -497,7 +497,7 @@
* [startDragToDesktop].
*/
fun cancelDragToDesktop(task: RunningTaskInfo) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: cancelDragToDesktop taskId=%d",
task.taskId
@@ -512,7 +512,7 @@
position: Point,
transitionSource: DesktopModeTransitionSource
) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToFullscreen with animation taskId=%d",
task.taskId
@@ -540,7 +540,7 @@
/** Move a task to the front */
fun moveTaskToFront(taskInfo: RunningTaskInfo) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveTaskToFront taskId=%d",
taskInfo.taskId
@@ -571,10 +571,10 @@
fun moveToNextDisplay(taskId: Int) {
val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
if (task == null) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
return
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"moveToNextDisplay: taskId=%d taskDisplayId=%d",
taskId,
@@ -589,7 +589,7 @@
newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId }
}
if (newDisplayId == null) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
return
}
moveToDisplay(task, newDisplayId)
@@ -601,7 +601,7 @@
* No-op if task is already on that display per [RunningTaskInfo.displayId].
*/
private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"moveToDisplay: taskId=%d displayId=%d",
task.taskId,
@@ -609,13 +609,13 @@
)
if (task.displayId == displayId) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
return
}
val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
if (displayAreaInfo == null) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
return
}
@@ -770,7 +770,7 @@
wct: WindowContainerTransaction,
newTaskIdInFront: Int? = null
): RunningTaskInfo? {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s",
newTaskIdInFront ?: "null"
@@ -815,7 +815,7 @@
}
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper")
val intent = Intent(context, DesktopWallpaperActivity::class.java)
val options =
ActivityOptions.makeBasic().apply {
@@ -835,7 +835,7 @@
private fun removeWallpaperActivity(wct: WindowContainerTransaction) {
desktopModeTaskRepository.wallpaperActivityToken?.let { token ->
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper")
wct.removeTask(token)
}
}
@@ -873,7 +873,7 @@
transition: IBinder,
request: TransitionRequestInfo
): WindowContainerTransaction? {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: handleRequest request=%s",
request
@@ -915,7 +915,7 @@
}
if (!shouldHandleRequest) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: skipping handleRequest reason=%s",
reason
@@ -939,7 +939,7 @@
}
}
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: handleRequest result=%s",
result ?: "null"
@@ -977,15 +977,15 @@
task: RunningTaskInfo,
transition: IBinder
): WindowContainerTransaction? {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
if (keyguardManager.isKeyguardLocked) {
// Do NOT handle freeform task launch when locked.
// It will be launched in fullscreen windowing mode (Details: b/160925539)
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
return null
}
if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: bring desktop tasks to front on transition" +
" taskId=%d",
@@ -1014,9 +1014,9 @@
task: RunningTaskInfo,
transition: IBinder
): WindowContainerTransaction? {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: switch fullscreen task to freeform on transition" +
" taskId=%d",
@@ -1059,7 +1059,7 @@
}
if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
// Could happen if the task hasn't been removed from closing list after it disappeared
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: the task with taskId=%d is already closing!",
task.taskId
@@ -1398,7 +1398,7 @@
if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) {
// TODO(b/320797628): Should only return early if there is an existing running task, and
// notify the user as well. But for now, just ignore the drop.
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
return false
}
@@ -1489,7 +1489,7 @@
private val listener: VisibleTasksListener =
object : VisibleTasksListener {
override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
@@ -1534,11 +1534,11 @@
}
override fun stashDesktopApps(displayId: Int) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: stashDesktopApps is deprecated")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: stashDesktopApps is deprecated")
}
override fun hideStashedDesktopApps(displayId: Int) {
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: hideStashedDesktopApps is deprecated"
)
@@ -1565,7 +1565,7 @@
}
override fun setTaskListener(listener: IDesktopTaskListener?) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: set task listener=%s",
listener ?: "null"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 3f5bd1a..534cc22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -23,12 +23,12 @@
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import androidx.annotation.VisibleForTesting
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
-import com.android.wm.shell.util.KtProtoLog
/**
* Limits the number of tasks shown in Desktop Mode.
@@ -71,7 +71,7 @@
if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return
if (!isTaskReorderedToBackOrInvisible(info, taskToMinimize)) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: task %d is not reordered to back nor invis",
taskToMinimize.taskId)
@@ -109,7 +109,7 @@
}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: transition %s finished", transition)
mPendingTransitionTokensAndTasks.remove(transition)
@@ -133,7 +133,7 @@
if (remainingMinimizedTasks.isEmpty()) {
return
}
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: removing leftover minimized tasks: $remainingMinimizedTasks")
remainingMinimizedTasks.forEach { taskIdToRemove ->
@@ -150,7 +150,7 @@
* finished so we don't minimize the task if the transition fails.
*/
private fun markTaskMinimized(displayId: Int, taskId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: marking %d as minimized", taskId)
taskRepository.minimizeTask(displayId, taskId)
@@ -169,7 +169,7 @@
wct: WindowContainerTransaction,
newFrontTaskInfo: RunningTaskInfo,
): RunningTaskInfo? {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d",
newFrontTaskInfo.taskId)
@@ -217,7 +217,7 @@
visibleFreeformTaskIdsOrderedFrontToBack: List<Int>
): RunningTaskInfo? {
if (visibleFreeformTaskIdsOrderedFrontToBack.size <= getMaxTaskLimit()) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: no need to minimize; tasks below limit")
// No need to minimize anything
@@ -227,7 +227,7 @@
shellTaskOrganizer.getRunningTaskInfo(
visibleFreeformTaskIdsOrderedFrontToBack.last())
if (taskToMinimize == null) {
- KtProtoLog.e(
+ ProtoLog.e(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: taskToMinimize == null")
return null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index f01f645..246fd92 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -21,35 +21,38 @@
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.util.KtProtoLog
/**
- * A [Transitions.TransitionObserver] that observes shell transitions and updates
- * the [DesktopModeTaskRepository] state TODO: b/332682201
- * This observes transitions related to desktop mode
- * and other transitions that originate both within and outside shell.
+ * A [Transitions.TransitionObserver] that observes shell transitions and updates the
+ * [DesktopModeTaskRepository] state TODO: b/332682201 This observes transitions related to desktop
+ * mode and other transitions that originate both within and outside shell.
*/
class DesktopTasksTransitionObserver(
context: Context,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val transitions: Transitions,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
shellInit: ShellInit
) : Transitions.TransitionObserver {
init {
- if (Transitions.ENABLE_SHELL_TRANSITIONS &&
- DesktopModeStatus.canEnterDesktopMode(context)) {
+ if (
+ Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.canEnterDesktopMode(context)
+ ) {
shellInit.addInitCallback(::onInit, this)
}
}
fun onInit() {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTasksTransitionObserver: onInit")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTasksTransitionObserver: onInit")
transitions.registerObserver(this)
}
@@ -83,8 +86,16 @@
change.taskInfo?.let { taskInfo ->
if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
when (change.mode) {
- WindowManager.TRANSIT_OPEN ->
+ WindowManager.TRANSIT_OPEN -> {
desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token
+ // After the task for the wallpaper is created, set it non-trimmable.
+ // This is important to prevent recents from trimming and removing the
+ // task.
+ shellTaskOrganizer.applyTransaction(
+ WindowContainerTransaction()
+ .setTaskTrimmableFromRecents(taskInfo.token, false)
+ )
+ }
WindowManager.TRANSIT_CLOSE ->
desktopModeTaskRepository.wallpaperActivityToken = null
else -> {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
index c4a4474..1c2415c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
@@ -21,8 +21,8 @@
import android.content.ComponentName
import android.os.Bundle
import android.view.WindowManager
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.util.KtProtoLog
/**
* A transparent activity used in the desktop mode to show the wallpaper under the freeform windows.
@@ -36,7 +36,7 @@
class DesktopWallpaperActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index d99b724..ddee8fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -29,6 +29,7 @@
import android.window.TransitionRequestInfo
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -42,7 +43,6 @@
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TransitionHandler
-import com.android.wm.shell.util.KtProtoLog
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
@@ -114,7 +114,7 @@
dragToDesktopAnimator: MoveToDesktopAnimator,
) {
if (inProgress) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DragToDesktop: Drag to desktop transition already in progress."
)
@@ -599,7 +599,7 @@
) {
val state = transitionState ?: return
if (aborted && state.startTransitionToken == transition) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DragToDesktop: onTransitionConsumed() start transition aborted"
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index b3c3a3d..e0b0866 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -52,6 +52,7 @@
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -353,6 +354,12 @@
pd.dragSession.initialize();
pd.activeDragCount++;
pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Hiding task surface: taskId=%d", pd.dragSession.hideDragSourceTaskId);
+ mShellTaskOrganizer.setTaskSurfaceVisibility(
+ pd.dragSession.hideDragSourceTaskId, false /* visible */);
+ }
setDropTargetWindowVisibility(pd, View.VISIBLE);
notifyListeners(l -> {
l.onDragStarted();
@@ -382,6 +389,13 @@
if (pd.dragLayout.hasDropped()) {
mLogger.logDrop();
} else {
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Re-showing task surface: taskId=%d",
+ pd.dragSession.hideDragSourceTaskId);
+ mShellTaskOrganizer.setTaskSurfaceVisibility(
+ pd.dragSession.hideDragSourceTaskId, true /* visible */);
+ }
pd.activeDragCount--;
pd.dragLayout.hide(event, () -> {
if (pd.activeDragCount == 0) {
@@ -435,7 +449,16 @@
private boolean handleDrop(DragEvent event, PerDisplay pd) {
final SurfaceControl dragSurface = event.getDragSurface();
pd.activeDragCount--;
- return pd.dragLayout.drop(event, dragSurface, () -> {
+ // Find the token of the task to hide as a part of entering split
+ WindowContainerToken hideTaskToken = null;
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ActivityManager.RunningTaskInfo info = mShellTaskOrganizer.getRunningTaskInfo(
+ pd.dragSession.hideDragSourceTaskId);
+ if (info != null) {
+ hideTaskToken = info.token;
+ }
+ }
+ return pd.dragLayout.drop(event, dragSurface, hideTaskToken, () -> {
if (pd.activeDragCount == 0) {
// Hide the window if another drag hasn't been started while animating the drop
setDropTargetWindowVisibility(pd, View.INVISIBLE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 9c7476d..95fe8b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -59,6 +59,7 @@
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import android.window.WindowContainerToken;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -234,8 +235,13 @@
return null;
}
+ /**
+ * Handles the drop on a given {@param target}. If a {@param hideTaskToken} is set, then the
+ * handling of the drop will attempt to hide the given task as a part of the same window
+ * container transaction if possible.
+ */
@VisibleForTesting
- void handleDrop(Target target) {
+ void handleDrop(Target target, @Nullable WindowContainerToken hideTaskToken) {
if (target == null || !mTargets.contains(target)) {
return;
}
@@ -254,16 +260,17 @@
? mFullscreenStarter
: mSplitscreenStarter;
if (mSession.appData != null) {
- launchApp(mSession, starter, position);
+ launchApp(mSession, starter, position, hideTaskToken);
} else {
- launchIntent(mSession, starter, position);
+ launchIntent(mSession, starter, position, hideTaskToken);
}
}
/**
* Launches an app provided by SysUI.
*/
- private void launchApp(DragSession session, Starter starter, @SplitPosition int position) {
+ private void launchApp(DragSession session, Starter starter, @SplitPosition int position,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
position);
final ClipDescription description = session.getClipDescription();
@@ -283,8 +290,12 @@
if (isTask) {
final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
- starter.startTask(taskId, position, opts);
+ starter.startTask(taskId, position, opts, hideTaskToken);
} else if (isShortcut) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Can not hide task token with starting shortcut");
+ }
final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
starter.startShortcut(packageName, id, position, opts, user);
@@ -297,14 +308,15 @@
}
}
starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
- position, opts);
+ position, opts, hideTaskToken);
}
}
/**
* Launches an intent sender provided by an application.
*/
- private void launchIntent(DragSession session, Starter starter, @SplitPosition int position) {
+ private void launchIntent(DragSession session, Starter starter, @SplitPosition int position,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
position);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
@@ -319,18 +331,20 @@
final Bundle opts = baseActivityOpts.toBundle();
starter.startIntent(session.launchableIntent,
session.launchableIntent.getCreatorUserHandle().getIdentifier(),
- null /* fillIntent */, position, opts);
+ null /* fillIntent */, position, opts, hideTaskToken);
}
/**
* Interface for actually committing the task launches.
*/
public interface Starter {
- void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options);
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken);
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user);
void startIntent(PendingIntent intent, int userId, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options);
+ @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken);
void enterSplitScreen(int taskId, boolean leftOrTop);
/**
@@ -352,7 +366,12 @@
}
@Override
- public void startTask(int taskId, int position, @Nullable Bundle options) {
+ public void startTask(int taskId, int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Default starter does not support hide task token");
+ }
try {
ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
} catch (RemoteException e) {
@@ -375,7 +394,12 @@
@Override
public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
- int position, @Nullable Bundle options) {
+ int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Default starter does not support hide task token");
+ }
try {
intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 910175e..f0514e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -51,8 +51,10 @@
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
+import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.ProtoLog;
@@ -483,13 +485,13 @@
/**
* Handles the drop onto a target and animates out the visible drop targets.
*/
- public boolean drop(DragEvent event, SurfaceControl dragSurface,
- Runnable dropCompleteCallback) {
+ public boolean drop(DragEvent event, @NonNull SurfaceControl dragSurface,
+ @Nullable WindowContainerToken hideTaskToken, Runnable dropCompleteCallback) {
final boolean handledDrop = mCurrentTarget != null;
mHasDropped = true;
// Process the drop
- mPolicy.handleDrop(mCurrentTarget);
+ mPolicy.handleDrop(mCurrentTarget, hideTaskToken);
// Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
@@ -499,7 +501,7 @@
return handledDrop;
}
- private void hideDragSurface(SurfaceControl dragSurface) {
+ private void hideDragSurface(@NonNull SurfaceControl dragSurface) {
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f);
// Currently the splash icon animation runs with the default ValueAnimator duration of
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 3bedef2..dcbdfa3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -27,6 +28,7 @@
import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.os.PersistableBundle;
import androidx.annotation.Nullable;
@@ -63,6 +65,7 @@
@WindowConfiguration.ActivityType
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean dragItemSupportsSplitscreen;
+ int hideDragSourceTaskId = -1;
DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data, int dragFlags) {
@@ -70,6 +73,11 @@
mInitialDragData = data;
mInitialDragFlags = dragFlags;
displayLayout = dispLayout;
+ hideDragSourceTaskId = data.getDescription().getExtras() != null
+ ? data.getDescription().getExtras().getInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, -1)
+ : -1;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Extracting drag source taskId: taskId=%d", hideDragSourceTaskId);
}
/**
@@ -84,16 +92,27 @@
* Updates the running task for this drag session.
*/
void updateRunningTask() {
+ final boolean hideDragSourceTask = hideDragSourceTaskId != -1;
final List<ActivityManager.RunningTaskInfo> tasks =
- mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
+ mActivityTaskManager.getTasks(hideDragSourceTask ? 2 : 1,
+ false /* filterOnlyVisibleRecents */);
if (!tasks.isEmpty()) {
- final ActivityManager.RunningTaskInfo task = tasks.get(0);
- runningTaskInfo = task;
- runningTaskWinMode = task.getWindowingMode();
- runningTaskActType = task.getActivityType();
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
- "Running task: id=%d component=%s", task.taskId,
- task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Skipping running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ continue;
+ }
+ runningTaskInfo = task;
+ runningTaskWinMode = task.getWindowingMode();
+ runningTaskActType = task.getActivityType();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ break;
+ }
}
}
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 fbb4bc4..9539a45 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
@@ -37,6 +37,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.IRecentsAnimationRunner;
+import android.window.WindowContainerToken;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -457,11 +458,31 @@
}
/**
- * Find the background task that match the given component.
+ * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified.
+ * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
+ */
+ @Nullable
+ public ActivityManager.RunningTaskInfo getTopRunningTask(
+ @Nullable WindowContainerToken ignoreTaskToken) {
+ List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(2,
+ false /* filterOnlyVisibleRecents */);
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (task.token.equals(ignoreTaskToken)) {
+ continue;
+ }
+ return task;
+ }
+ return null;
+ }
+
+ /**
+ * Find the background task that match the given component. Ignores tasks match
+ * {@param ignoreTaskToken} if it is non-null.
*/
@Nullable
public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
- int userId) {
+ int userId, @Nullable WindowContainerToken ignoreTaskToken) {
if (componentName == null) {
return null;
}
@@ -473,6 +494,9 @@
if (task.isVisible) {
continue;
}
+ if (task.token.equals(ignoreTaskToken)) {
+ continue;
+ }
if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) {
return task;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index b4941a5..e659151 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -64,6 +64,7 @@
import android.view.WindowManager;
import android.widget.Toast;
import android.window.RemoteTransition;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -526,7 +527,15 @@
mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds);
}
- public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ /**
+ * Starts an existing task into split.
+ * TODO(b/351900580): We should remove this path and use StageCoordinator#startTask() instead
+ * @param hideTaskToken is not supported.
+ */
+ public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Legacy startTask does not support hide task token");
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@Override
@@ -584,8 +593,8 @@
if (options == null) options = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
- if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
- user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
+ if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null),
+ user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) {
if (mMultiInstanceHelpher.supportsMultiInstanceSplit(
getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
@@ -676,10 +685,11 @@
* See {@link #startIntent(PendingIntent, int, Intent, int, Bundle)}
* @param instanceId to be used by {@link SplitscreenEventLogger}
*/
- public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
+ public void startIntentWithInstanceId(PendingIntent intent, int userId,
+ @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options,
+ @NonNull InstanceId instanceId) {
mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
- startIntent(intent, userId, fillInIntent, position, options);
+ startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */);
}
private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
@@ -825,9 +835,15 @@
instanceId);
}
+ /**
+ * Starts the given intent into split.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
@Override
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
+ @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
fillInIntent, position);
@@ -838,23 +854,24 @@
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
final String packageName1 = SplitScreenUtils.getPackageName(intent);
- final String packageName2 = getPackageName(reverseSplitPosition(position));
- final int userId2 = getUserId(reverseSplitPosition(position));
+ final String packageName2 = getPackageName(reverseSplitPosition(position), hideTaskToken);
+ final int userId2 = getUserId(reverseSplitPosition(position), hideTaskToken);
final ComponentName component = intent.getIntent().getComponent();
// To prevent accumulating large number of instances in the background, reuse task
// in the background. If we don't explicitly reuse, new may be created even if the app
// isn't multi-instance because WM won't automatically remove/reuse the previous instance
final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
+ .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1,
+ hideTaskToken))
.orElse(null);
if (taskInfo != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Found suitable background task=%s", taskInfo);
if (ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startTask(taskInfo.taskId, position, options);
+ mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken);
} else {
- startTask(taskInfo.taskId, position, options);
+ startTask(taskInfo.taskId, position, options, hideTaskToken);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
return;
@@ -879,19 +896,23 @@
}
}
- mStageCoordinator.startIntent(intent, fillInIntent, position, options);
+ mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken);
}
- /** Retrieve package name of a specific split position if split screen is activated, otherwise
- * returns the package name of the top running task. */
+ /**
+ * Retrieve package name of a specific split position if split screen is activated, otherwise
+ * returns the package name of the top running task.
+ * TODO(b/351900580): Merge this with getUserId() so we don't make multiple binder calls
+ */
@Nullable
- private String getPackageName(@SplitPosition int position) {
+ private String getPackageName(@SplitPosition int position,
+ @Nullable WindowContainerToken ignoreTaskToken) {
ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
taskInfo = getTaskInfo(position);
} else {
taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask())
+ .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
.orElse(null);
if (!isValidToSplit(taskInfo)) {
return null;
@@ -901,15 +922,19 @@
return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null;
}
- /** Retrieve user id of a specific split position if split screen is activated, otherwise
- * returns the user id of the top running task. */
- private int getUserId(@SplitPosition int position) {
+ /**
+ * Retrieve user id of a specific split position if split screen is activated, otherwise
+ * returns the user id of the top running task.
+ * TODO: Merge this with getPackageName() so we don't make multiple binder calls
+ */
+ private int getUserId(@SplitPosition int position,
+ @Nullable WindowContainerToken ignoreTaskToken) {
ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
taskInfo = getTaskInfo(position);
} else {
taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask())
+ .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
.orElse(null);
if (!isValidToSplit(taskInfo)) {
return -1;
@@ -1290,7 +1315,8 @@
@Override
public void startTask(int taskId, int position, @Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> controller.startTask(taskId, position, options));
+ (controller) -> controller.startTask(taskId, position, options,
+ null /* hideTaskToken */));
}
@Override
@@ -1402,8 +1428,8 @@
public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
@Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> controller.startIntent(intent, userId, fillInIntent, position,
- options, instanceId));
+ (controller) -> controller.startIntentWithInstanceId(intent, userId,
+ fillInIntent, position, options, instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index d9e9776..4104234 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -592,12 +592,21 @@
}
}
- /** Use this method to launch an existing Task via a taskId */
- void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ /**
+ * Use this method to launch an existing Task via a taskId.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position);
mSplitRequest = new SplitRequest(taskId, position);
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ if (hideTaskToken != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
+ wct.reorder(hideTaskToken, false /* onTop */);
+ }
wct.startTask(taskId, options);
// If this should be mixed, send the task to avoid split handle transition directly.
if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) {
@@ -623,9 +632,13 @@
extraTransitType, !mIsDropEntering);
}
- /** Launches an activity into split. */
+ /**
+ * Launches an activity into split.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options) {
+ @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(),
position);
mSplitRequest = new SplitRequest(intent.getIntent(), position);
@@ -636,6 +649,10 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ if (hideTaskToken != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
+ wct.reorder(hideTaskToken, false /* onTop */);
+ }
wct.sendPendingIntent(intent, fillInIntent, options);
// If this should be mixed, just send the intent to avoid split handle transition directly.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index 3a68009..dd4595a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -27,6 +27,7 @@
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
@@ -75,6 +76,7 @@
*/
@VisibleForTesting
public void init() {
+ ProtoLog.registerGroups(ShellProtoLogGroup.values());
ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size());
SurfaceControl.setDebugUsageAfterRelease(true);
// Init in order of registration
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
deleted file mode 100644
index ee6c81a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.util
-
-import android.util.Log
-import com.android.internal.protolog.common.IProtoLogGroup
-import com.android.internal.protolog.ProtoLog
-
-/**
- * Log messages using an API similar to [com.android.internal.protolog.ProtoLog]. Useful for
- * logging from Kotlin classes as ProtoLog does not have support for Kotlin.
- *
- * All messages are logged to logcat if logging is enabled for that [IProtoLogGroup].
- */
-// TODO(b/168581922): remove once ProtoLog adds support for Kotlin
-class KtProtoLog {
- companion object {
- /** @see [com.android.internal.protolog.ProtoLog.d] */
- fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.d(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.ProtoLog.v] */
- fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.v(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.ProtoLog.i] */
- fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.i(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.ProtoLog.w] */
- fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.w(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.ProtoLog.e] */
- fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.e(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.ProtoLog.wtf] */
- fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.wtf(group.tag, String.format(messageString, *args))
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 1be33e5..faf6a62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
@@ -41,12 +42,17 @@
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -410,6 +416,26 @@
decoration.closeMaximizeMenu();
}
+ private void onOpenInBrowser(@NonNull DesktopModeWindowDecoration decor, @NonNull Uri uri) {
+ openInBrowser(uri);
+ decor.closeHandleMenu();
+ decor.closeMaximizeMenu();
+ }
+
+ private void openInBrowser(Uri uri) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .setComponent(getDefaultBrowser())
+ .addFlags(FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ private ComponentName getDefaultBrowser() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
+ final ResolveInfo info = mContext.getPackageManager()
+ .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ return info.getComponentInfo().getComponentName();
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -489,6 +515,10 @@
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
mDesktopTasksController.requestSplit(decoration.mTaskInfo);
+ } else if (id == R.id.open_in_browser_button) {
+ // TODO(b/346441962): let the decoration handle the click gesture and only call back
+ // to the ViewModel via #setOpenInBrowserClickListener
+ decoration.onOpenInBrowserClick();
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.maximize_window) {
@@ -1091,6 +1121,7 @@
windowDecoration.setOnRightSnapClickListener((taskId, tag) -> {
onSnapResize(taskId, false /* isLeft */);
});
+ windowDecoration.setOpenInBrowserClickListener(this::onOpenInBrowser);
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index b62194c..5ffd883 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -44,6 +44,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Handler;
import android.os.Trace;
import android.util.Log;
@@ -88,6 +89,7 @@
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
+ private static final int CAPTURED_LINK_TIMEOUT_MS = 7000;
@VisibleForTesting
static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
@@ -124,6 +126,8 @@
private Bitmap mResizeVeilBitmap;
private CharSequence mAppName;
+ private CapturedLink mCapturedLink;
+ private OpenInBrowserClickListener mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
@@ -138,6 +142,7 @@
// being hovered. There's a small delay after stopping the hover, to allow a quick reentry
// to cancel the close.
private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
+ private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
DesktopModeWindowDecoration(
Context context,
@@ -153,8 +158,7 @@
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {},
- DefaultMaximizeMenuFactory.INSTANCE);
+ new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE);
}
DesktopModeWindowDecoration(
@@ -232,6 +236,10 @@
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
+ void setOpenInBrowserClickListener(OpenInBrowserClickListener listener) {
+ mOpenInBrowserClickListener = listener;
+ }
+
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
@@ -323,6 +331,11 @@
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
+
+ if (Flags.enableDesktopWindowingAppToWeb()) {
+ setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
+ }
+
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
@@ -367,6 +380,28 @@
Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
}
+ private void setCapturedLink(Uri capturedLink, long timeStamp) {
+ if (capturedLink == null
+ || (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) {
+ return;
+ }
+ mCapturedLink = new CapturedLink(capturedLink, timeStamp);
+ mHandler.postDelayed(mCapturedLinkExpiredRunnable, CAPTURED_LINK_TIMEOUT_MS);
+ }
+
+ private void onCapturedLinkExpired() {
+ mHandler.removeCallbacks(mCapturedLinkExpiredRunnable);
+ if (mCapturedLink != null) {
+ mCapturedLink.setExpired();
+ }
+ }
+
+ void onOpenInBrowserClick() {
+ if (mOpenInBrowserClickListener == null || mCapturedLink == null) return;
+ mOpenInBrowserClickListener.onClick(this, mCapturedLink.mUri);
+ onCapturedLinkExpired();
+ }
+
private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
if (!isDragResizable(mTaskInfo)) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
@@ -827,11 +862,17 @@
.setCaptionHeight(mResult.mCaptionHeight)
.setDisplayController(mDisplayController)
.setSplitScreenController(splitScreenController)
+ .setBrowserLinkAvailable(browserLinkAvailable())
.build();
mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
}
+ @VisibleForTesting
+ boolean browserLinkAvailable() {
+ return mCapturedLink != null && !mCapturedLink.mExpired;
+ }
+
/**
* Close the handle menu window.
*/
@@ -1121,6 +1162,31 @@
}
}
+ @VisibleForTesting
+ static class CapturedLink {
+ private final long mTimeStamp;
+ private final Uri mUri;
+ private boolean mExpired;
+
+ CapturedLink(@NonNull Uri uri, long timeStamp) {
+ mUri = uri;
+ mTimeStamp = timeStamp;
+ mExpired = false;
+ }
+
+ void setExpired() {
+ mExpired = true;
+ }
+ }
+
+
+ /** Listener for the handle menu's "Open in browser" button */
+ interface OpenInBrowserClickListener {
+
+ /** Inform the implementing class that the "Open in browser" button has been clicked */
+ void onClick(DesktopModeWindowDecoration decoration, Uri uri);
+ }
+
interface ExclusionRegionListener {
/** Inform the implementing class of this task's change in region resize handles */
void onExclusionRegionChanged(int taskId, Region region);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index df0836c..7e44f32 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -42,6 +42,7 @@
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
+import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -81,6 +82,7 @@
// those as well.
final Point mGlobalMenuPosition = new Point();
private final boolean mShouldShowWindowingPill;
+ private final boolean mShouldShowBrowserPill;
private final Bitmap mAppIconBitmap;
private final CharSequence mAppName;
private final View.OnClickListener mOnClickListener;
@@ -101,7 +103,7 @@
View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
Bitmap appIcon, CharSequence appName, DisplayController displayController,
SplitScreenController splitScreenController, boolean shouldShowWindowingPill,
- int captionHeight) {
+ boolean shouldShowBrowserPill, int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
@@ -113,6 +115,7 @@
mAppIconBitmap = appIcon;
mAppName = appName;
mShouldShowWindowingPill = shouldShowWindowingPill;
+ mShouldShowBrowserPill = shouldShowBrowserPill;
mCaptionHeight = captionHeight;
loadHandleMenuDimensions();
updateHandleMenuPillPositions();
@@ -170,6 +173,7 @@
setupWindowingPill(handleMenu);
}
setupMoreActionsPill(handleMenu);
+ setupOpenInBrowserPill(handleMenu);
}
/**
@@ -228,6 +232,15 @@
}
}
+ private void setupOpenInBrowserPill(View handleMenu) {
+ if (!mShouldShowBrowserPill) {
+ handleMenu.findViewById(R.id.open_in_browser_pill).setVisibility(View.GONE);
+ return;
+ }
+ final Button browserButton = handleMenu.findViewById(R.id.open_in_browser_button);
+ browserButton.setOnClickListener(mOnClickListener);
+ }
+
/**
* Returns array of windowing icon color based on current UI theme. First element of the
* array is for inactive icons and the second is for active icons.
@@ -423,6 +436,10 @@
menuHeight -= loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
}
+ if (!mShouldShowBrowserPill) {
+ menuHeight -= loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height);
+ }
return menuHeight;
}
@@ -457,6 +474,7 @@
private int mCaptionHeight;
private DisplayController mDisplayController;
private SplitScreenController mSplitScreenController;
+ private boolean mShowBrowserPill;
Builder(@NonNull DesktopModeWindowDecoration parent) {
mParent = parent;
@@ -507,10 +525,15 @@
return this;
}
+ Builder setBrowserLinkAvailable(Boolean showBrowserPill) {
+ mShowBrowserPill = showBrowserPill;
+ return this;
+ }
+
HandleMenu build() {
return new HandleMenu(mParent, mLayoutId, mOnClickListener,
mOnTouchListener, mAppIcon, mName, mDisplayController, mSplitScreenController,
- mShowWindowingPill, mCaptionHeight);
+ mShowWindowingPill, mShowBrowserPill, mCaptionHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index 8c5d4a2..25a829b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -26,6 +26,7 @@
import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
import android.view.ViewGroup
+import android.widget.Button
import androidx.core.animation.doOnEnd
import androidx.core.view.children
import com.android.wm.shell.R
@@ -72,6 +73,7 @@
private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
+ private val openInBrowserPill: ViewGroup = handleMenu.requireViewById(R.id.open_in_browser_pill)
/** Animates the opening of the handle menu. */
fun animateOpen() {
@@ -80,6 +82,7 @@
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
+ animateOpenInBrowserPill()
runAnimations()
}
@@ -94,6 +97,7 @@
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
+ animateOpenInBrowserPill()
runAnimations()
}
@@ -109,6 +113,7 @@
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
+ openInBrowserPillClose()
runAnimations(after)
}
@@ -125,6 +130,7 @@
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
+ openInBrowserPillClose()
runAnimations(after)
}
@@ -137,6 +143,7 @@
appInfoPill.children.forEach { it.alpha = 0f }
windowingPill.alpha = 0f
moreActionsPill.alpha = 0f
+ openInBrowserPill.alpha = 0f
// Setup pivots.
handleMenu.pivotX = menuWidth / 2f
@@ -147,6 +154,9 @@
moreActionsPill.pivotX = menuWidth / 2f
moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
+
+ openInBrowserPill.pivotX = menuWidth / 2f
+ openInBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat()
}
private fun animateAppInfoPillOpen() {
@@ -268,12 +278,50 @@
// More Actions Content Opacity Animation
moreActionsPill.children.forEach {
animators +=
- ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_CONTENT_ALPHA_OPEN_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ }
+ }
+
+ private fun animateOpenInBrowserPill() {
+ // Open in Browser X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ // Open in Browser Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_ALPHA_OPEN_DURATION
+ }
+
+ // Open in Browser Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_OPEN_DELAY
+ duration = BODY_ELEVATION_OPEN_DURATION
+ }
+
+ // Open in Browser Button Opacity Animation
+ val button = openInBrowserPill.requireViewById<Button>(R.id.open_in_browser_button)
+ animators +=
+ ObjectAnimator.ofFloat(button, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_OPEN_DELAY
duration = BODY_CONTENT_ALPHA_OPEN_DURATION
interpolator = Interpolators.FAST_OUT_SLOW_IN
}
- }
}
private fun appInfoPillCollapse() {
@@ -379,6 +427,37 @@
}
}
+ private fun openInBrowserPillClose() {
+ // Open in Browser X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // Open in Browser Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // Upward Open in Browser y-translation Animation
+ val yStart: Float = -captionHeight / 2
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Y, yStart).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+ }
+
/**
* Runs the list of hide animators concurrently.
*
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
index c725b08..430f80b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
@@ -20,6 +20,7 @@
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
+import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
@@ -45,27 +46,30 @@
FlickerConfigEntry(
scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"),
extractor =
- ShellTransitionScenarioExtractor(
- transitionMatcher =
- object : ITransitionMatcher {
- override fun findAll(
- transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
- it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ // TODO(351168217) Use jank CUJ to extract a longer trace
+ it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP
+ }
+ }
}
- }
- }
- ),
+ ),
assertions =
- AssertionTemplates.COMMON_ASSERTIONS +
+ AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
- AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
- AppWindowHasDesktopModeInitialBoundsAtTheEnd(
- Components.DESKTOP_MODE_APP
+ AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(
+ Components.DESKTOP_MODE_APP
+ ),
+ AppWindowBecomesVisible(DESKTOP_WALLPAPER)
)
- ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
// Use this scenario for closing an app in desktop windowing, except the last app. For the
@@ -74,63 +78,65 @@
FlickerConfigEntry(
scenarioId = ScenarioId("CLOSE_APP"),
extractor =
- ShellTransitionScenarioExtractor(
- transitionMatcher =
- object : ITransitionMatcher {
- override fun findAll(
- transitions: Collection<Transition>
- ): Collection<Transition> {
- // In case there are multiple windows closing, filter out the
- // last window closing. It should use the CLOSE_LAST_APP
- // scenario below.
- return transitions
- .filter { it.type == TransitionType.CLOSE }
- .sortedByDescending { it.id }
- .drop(1)
- }
- }
- ),
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ // In case there are multiple windows closing, filter out the
+ // last window closing. It should use the CLOSE_LAST_APP
+ // scenario below.
+ return transitions
+ .filter { it.type == TransitionType.CLOSE }
+ .sortedByDescending { it.id }
+ .drop(1)
+ }
+ }
+ ),
assertions =
- AssertionTemplates.COMMON_ASSERTIONS +
+ AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP),
+ AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP),
+ AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
+ )
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val CLOSE_LAST_APP =
FlickerConfigEntry(
scenarioId = ScenarioId("CLOSE_LAST_APP"),
extractor =
- ShellTransitionScenarioExtractor(
- transitionMatcher =
- object : ITransitionMatcher {
- override fun findAll(
- transitions: Collection<Transition>
- ): Collection<Transition> {
- val lastTransition =
- transitions
- .filter { it.type == TransitionType.CLOSE }
- .maxByOrNull { it.id }!!
- return listOf(lastTransition)
- }
- }
- ),
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ val lastTransition =
+ transitions
+ .filter { it.type == TransitionType.CLOSE }
+ .maxByOrNull { it.id }!!
+ return listOf(lastTransition)
+ }
+ }
+ ),
assertions =
- AssertionTemplates.COMMON_ASSERTIONS +
+ AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP),
- AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
- ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AppWindowIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
+ LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP),
+ AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
+ )
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val CORNER_RESIZE =
FlickerConfigEntry(
scenarioId = ScenarioId("CORNER_RESIZE"),
extractor =
- TaggedScenarioExtractorBuilder()
+ TaggedScenarioExtractorBuilder()
.setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
.setTransitionMatcher(
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
@@ -143,16 +149,16 @@
FlickerConfigEntry(
scenarioId = ScenarioId("CORNER_RESIZE_TO_MINIMUM_SIZE"),
extractor =
- TaggedScenarioExtractorBuilder()
+ TaggedScenarioExtractorBuilder()
.setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
.setTransitionMatcher(
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
- ).build(),
+ )
+ .build(),
assertions =
- AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
- listOf(
- AppWindowHasSizeOfAtLeast(Components.DESKTOP_MODE_APP, 770, 700)
- ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(AppWindowHasSizeOfAtLeast(Components.DESKTOP_MODE_APP, 770, 700))
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
index 3f2603a..35b2f56 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
@@ -107,7 +107,7 @@
instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
test_config_template: "AndroidTestTemplate.xml",
srcs: [
- ":WMShellFlickerTestsSplitScreenGroup1-src",
+ ":WMShellFlickerTestsSplitScreenGroup2-src",
],
static_libs: [
"WMShellFlickerTestsBase",
@@ -124,7 +124,7 @@
instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
test_config_template: "AndroidTestTemplate.xml",
srcs: [
- ":WMShellFlickerTestsSplitScreenGroup1-src",
+ ":WMShellFlickerTestsSplitScreenGroup3-src",
],
static_libs: [
"WMShellFlickerTestsBase",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 636c632..f5847cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -70,7 +70,7 @@
mContext,
configuration, mCallbacks);
splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
- mDividerView = spy((DividerView) splitWindowManager.getDividerView());
+ mDividerView = spy(splitWindowManager.getDividerView());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 582fb91..97fa8d6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -289,9 +289,9 @@
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */);
verify(mFullscreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_UNDEFINED), any());
+ eq(SPLIT_POSITION_UNDEFINED), any(), any());
}
private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) {
@@ -304,14 +304,14 @@
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
}
private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) {
@@ -324,14 +324,15 @@
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM),
+ null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
index 00f37da..b1d62f4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
@@ -26,6 +26,7 @@
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.Companion.convertToToggleOverrideWithFallback
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_ON
@@ -286,9 +287,9 @@
@Test
@EnableFlags(
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun isEnabled_dwFlagEnabled_overrideUnset_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_UNSET.setting)
@@ -308,9 +309,9 @@
@Test
@EnableFlags(
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun isEnabled_dwFlagEnabled_overrideOn_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_ON.setting)
@@ -330,9 +331,9 @@
@Test
@EnableFlags(
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun isEnabled_dwFlagEnabled_overrideOff_featureFlagOn_returnsFalse() {
setOverride(OVERRIDE_OFF.setting)
@@ -352,7 +353,7 @@
@Test
@EnableFlags(
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_UNSET.setting)
@@ -364,7 +365,7 @@
@Test
@EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags(
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOff_returnsFalse() {
setOverride(OVERRIDE_UNSET.setting)
@@ -374,7 +375,7 @@
@Test
@EnableFlags(
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_ON.setting)
@@ -386,7 +387,7 @@
@Test
@EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags(
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOff_returnTrue() {
setOverride(OVERRIDE_ON.setting)
@@ -396,7 +397,7 @@
@Test
@EnableFlags(
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_OFF.setting)
@@ -408,7 +409,7 @@
@Test
@EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags(
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOff_returnsFalse() {
setOverride(OVERRIDE_OFF.setting)
@@ -416,6 +417,19 @@
assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
}
+ @Test
+ fun convertToToggleOverrideWithFallback_validInt_returnsToggleOverride() {
+ assertThat(convertToToggleOverrideWithFallback(0, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_OFF)
+ assertThat(convertToToggleOverrideWithFallback(1, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_ON)
+ assertThat(convertToToggleOverrideWithFallback(-1, OVERRIDE_ON)).isEqualTo(OVERRIDE_UNSET)
+ }
+
+ @Test
+ fun convertToToggleOverrideWithFallback_invalidInt_returnsFallback() {
+ assertThat(convertToToggleOverrideWithFallback(2, OVERRIDE_ON)).isEqualTo(OVERRIDE_ON)
+ assertThat(convertToToggleOverrideWithFallback(-2, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_UNSET)
+ }
+
private fun setOverride(setting: Int?) {
val contentResolver = mContext.contentResolver
val key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 3c387f0..5b95b15 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -36,6 +36,7 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -49,6 +50,9 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
+import android.os.IBinder;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -195,10 +199,10 @@
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
}
@@ -213,19 +217,20 @@
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
}
@Test
public void startIntent_multiInstancesNotSupported_startTaskInBackgroundBeforeSplitActivated() {
- doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -233,15 +238,16 @@
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
- doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt());
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull());
+ isNull(), isNull());
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@@ -249,7 +255,7 @@
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
- doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -261,13 +267,13 @@
SPLIT_POSITION_BOTTOM_OR_RIGHT);
// Put the same component into a task in the background
doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
- .findTaskInBackground(any(), anyInt());
+ .findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull());
+ isNull(), isNull());
}
@Test
@@ -284,7 +290,7 @@
SPLIT_POSITION_BOTTOM_OR_RIGHT);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).switchSplitPosition(anyString());
}
@@ -312,6 +318,7 @@
info.supportsMultiWindow = true;
info.baseIntent = strIntent;
info.baseActivity = strIntent.getComponent();
+ info.token = new WindowContainerToken(mock(IWindowContainerToken.class));
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = info.baseActivity.getPackageName();
activityInfo.name = info.baseActivity.getClassName();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index b355137..d860609 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -31,6 +31,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
@@ -50,6 +51,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PointF;
+import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
import android.platform.test.annotations.DisableFlags;
@@ -118,6 +120,8 @@
private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
"persist.wm.debug.desktop_use_rounded_corners";
+ private static final Uri TEST_URI = Uri.parse("www.google.com");
+
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@Mock
@@ -150,6 +154,8 @@
private PackageManager mMockPackageManager;
@Mock
private Handler mMockHandler;
+ @Mock
+ private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -555,6 +561,65 @@
verify(mMockHandler).removeCallbacks(any());
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ // Assert captured link is set
+ assertTrue(decor.browserLinkAvailable());
+ // Asset runnable posted to set captured link to expired
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkNotResetToSameLink() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+
+ // Set captured link and run on captured link expired runnable
+ decor.relayout(taskInfo);
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+
+ decor.relayout(taskInfo);
+ // Assert captured link not set to same value twice
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkExpiresAfterClick() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ // Assert captured link is set
+ assertTrue(decor.browserLinkAvailable());
+ decor.onOpenInBrowserClick();
+ //Assert Captured link expires after button is clicked
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_openInBrowserListenerCalledOnClick() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ decor.onOpenInBrowserClick();
+
+ verify(mMockOpenInBrowserClickListener).onClick(any(), any());
+ }
+
private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
final OnTaskActionClickListener l = (taskId, tag) -> {};
decoration.setOnMaximizeOrRestoreClickListener(l);
@@ -595,11 +660,11 @@
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
- mMockSurfaceControlViewHostFactory,
- maximizeMenuFactory);
+ mMockSurfaceControlViewHostFactory, maximizeMenuFactory);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
+ windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
return windowDecor;
}
@@ -615,6 +680,8 @@
"DesktopModeWindowDecorationTests");
taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
"DesktopModeWindowDecorationTests");
+ taskInfo.capturedLink = TEST_URI;
+ taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 5582e0f..0c50ab6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -196,9 +196,9 @@
R.layout.desktop_mode_app_header
}
val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
- onClickListener, onTouchListener, appIcon, appName, displayController,
- splitScreenController, true /* shouldShowWindowingPill */,
- 50 /* captionHeight */ )
+ onClickListener, onTouchListener, appIcon, appName, displayController,
+ splitScreenController, true /* shouldShowWindowingPill */,
+ true /* shouldShowBrowserPill */, 50 /* captionHeight */)
handleMenu.show()
return handleMenu
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 124f1f0..25c767a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7435,6 +7435,21 @@
}
/**
+ * @hide
+ * Queries the volume policy
+ * @return the volume policy currently in use
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ public @NonNull VolumePolicy getVolumePolicy() {
+ try {
+ return getService().getVolumePolicy();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Set Hdmi Cec system audio mode.
*
* @param on whether to be on system audio mode
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 08cc126..d42b256 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -386,6 +386,8 @@
void setVolumePolicy(in VolumePolicy policy);
+ VolumePolicy getVolumePolicy();
+
boolean hasRegisteredDynamicPolicy();
void registerRecordingCallback(in IRecordingConfigDispatcher rcdb);
diff --git a/media/java/android/media/VolumePolicy.java b/media/java/android/media/VolumePolicy.java
index b193b70..96de72d 100644
--- a/media/java/android/media/VolumePolicy.java
+++ b/media/java/android/media/VolumePolicy.java
@@ -16,39 +16,53 @@
package android.media;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/** @hide */
+@TestApi
+@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final class VolumePolicy implements Parcelable {
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @NonNull
public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, false, 400);
/**
* Accessibility volume policy where the STREAM_MUSIC volume (i.e. media volume) affects
* the STREAM_ACCESSIBILITY volume, and vice-versa.
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public static final int A11Y_MODE_MEDIA_A11Y_VOLUME = 0;
/**
* Accessibility volume policy where the STREAM_ACCESSIBILITY volume is independent from
* any other volume.
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public static final int A11Y_MODE_INDEPENDENT_A11Y_VOLUME = 1;
/** Allow volume adjustments lower from vibrate to enter ringer mode = silent */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final boolean volumeDownToEnterSilent;
/** Allow volume adjustments higher to exit ringer mode = silent */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final boolean volumeUpToExitSilent;
/** Automatically enter do not disturb when ringer mode = silent */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final boolean doNotDisturbWhenSilent;
/** Only allow volume adjustment from vibrate to silent after this
number of milliseconds since an adjustment from normal to vibrate. */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final int vibrateToSilentDebounce;
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public VolumePolicy(boolean volumeDownToEnterSilent, boolean volumeUpToExitSilent,
boolean doNotDisturbWhenSilent, int vibrateToSilentDebounce) {
this.volumeDownToEnterSilent = volumeDownToEnterSilent;
@@ -82,19 +96,22 @@
&& other.vibrateToSilentDebounce == vibrateToSilentDebounce;
}
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@Override
public int describeContents() {
return 0;
}
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(volumeDownToEnterSilent ? 1 : 0);
dest.writeInt(volumeUpToExitSilent ? 1 : 0);
dest.writeInt(doNotDisturbWhenSilent ? 1 : 0);
dest.writeInt(vibrateToSilentDebounce);
}
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public static final @android.annotation.NonNull Parcelable.Creator<VolumePolicy> CREATOR
= new Parcelable.Creator<VolumePolicy>() {
@Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 66ab81b..e005334 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -554,11 +554,18 @@
mSelectedDevice = requireNonNull(selectedDevice);
Slog.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
-
+ // The permission consent dialog should not be displayed if it's a isSkipPrompt(true)
+ // AssociationRequest or when there is no device profile available
+ // for the multiple devices dialog.
+ // See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+ final String deviceProfile = mRequest.getDeviceProfile();
+ if (deviceProfile == null || mRequest.isSkipPrompt()) {
+ onUserSelectedDevice(mSelectedDevice);
+ return;
+ }
+ // The permission consent dialog should be displayed for the multiple device
+ // dialog if a device profile exists.
updateSingleDeviceUi();
-
- if (mRequest.isSkipPrompt()) return;
-
mSummary.setVisibility(View.VISIBLE);
mButtonAllow.setVisibility(View.VISIBLE);
mButtonNotAllow.setVisibility(View.VISIBLE);
@@ -588,9 +595,6 @@
if (deviceProfile == null && mRequest.isSingleDevice()) {
summary = getHtmlFromResources(this, summaryResourceId, remoteDeviceName);
mConstraintList.setVisibility(View.GONE);
- } else if (deviceProfile == null) {
- onUserSelectedDevice(mSelectedDevice);
- return;
} else {
summary = getHtmlFromResources(
this, summaryResourceId, getString(R.string.device_type));
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 4834039..b56b944 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -20,6 +20,7 @@
static_libs: [
"androidx.annotation_annotation",
"androidx.core_core",
+ "androidx.activity_activity",
"com.google.android.material_material",
"SettingsLibSettingsTransition",
"SettingsLibSettingsTheme",
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
index 8b27626..4659051 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
@@ -64,6 +64,7 @@
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
+ EdgeToEdgeUtils.enable(this);
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
DynamicColors.applyToActivityIfAvailable(this);
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 86ce2ab..3965303 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -57,6 +57,7 @@
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
+ EdgeToEdgeUtils.enable(this);
super.onCreate(savedInstanceState);
// for backward compatibility on R devices or wearable devices due to small device size.
if (mCustomizeLayoutResId > 0 && (Build.VERSION.SDK_INT < Build.VERSION_CODES.S
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java
new file mode 100644
index 0000000..6e53012
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 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 com.android.settingslib.collapsingtoolbar;
+
+import android.os.Build;
+
+import androidx.activity.ComponentActivity;
+import androidx.activity.EdgeToEdge;
+import androidx.annotation.NonNull;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+/**
+ * Util class for edge to edge.
+ */
+public class EdgeToEdgeUtils {
+ private EdgeToEdgeUtils() {
+ }
+
+ /**
+ * Enable Edge to Edge and handle overlaps using insets. It should be called before
+ * setContentView.
+ */
+ static void enable(@NonNull ComponentActivity activity) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ return;
+ }
+
+ EdgeToEdge.enable(activity);
+
+ ViewCompat.setOnApplyWindowInsetsListener(activity.findViewById(android.R.id.content),
+ (v, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(
+ WindowInsetsCompat.Type.systemBars()
+ | WindowInsetsCompat.Type.ime()
+ | WindowInsetsCompat.Type.displayCutout());
+ int statusBarHeight = activity.getWindow().getDecorView().getRootWindowInsets()
+ .getInsets(WindowInsetsCompat.Type.statusBars()).top;
+ // Apply the insets paddings to the view.
+ v.setPadding(insets.left, statusBarHeight, insets.right, insets.bottom);
+
+ // Return CONSUMED if you don't want the window insets to keep being
+ // passed down to descendant views.
+ return WindowInsetsCompat.CONSUMED;
+ });
+ }
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index 6052be3..b6e80c7 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -22,6 +22,9 @@
<item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
<item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item>
<item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
+ <!-- Set up edge-to-edge configuration for top app bar -->
+ <item name="android:clipToPadding">false</item>
+ <item name="android:clipChildren">false</item>
</style>
<style name="Theme.SettingsBase" parent="Theme.SettingsBase_v35" />
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index c9934ad..fb23637 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -115,7 +115,7 @@
content: @Composable (SwitchPreferenceModel) -> Unit,
) {
val context = LocalContext.current
- val restrictedSwitchPreferenceModel = remember(restrictedMode, model.title) {
+ val restrictedSwitchPreferenceModel = remember(restrictedMode, model) {
RestrictedSwitchPreferenceModel(context, model, restrictedMode)
}
restrictedSwitchPreferenceModel.RestrictionWrapper {
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 38c871c..403e219 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -102,3 +102,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "extreme_power_low_state_vulnerability"
+ namespace: "pixel_energizer"
+ description: "the battery saver can pause all non-essential apps and their corresponding notification when device is in locked state to introduce the security vulnerability"
+ bug: "346513692"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml
index 67139b5..f89fe93 100644
--- a/packages/SettingsLib/res/values/colors.xml
+++ b/packages/SettingsLib/res/values/colors.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<color name="disabled_text_color">#66000000</color> <!-- 38% black -->
<color name="bt_color_icon_1">#b4a50e0e</color> <!-- 72% Material Red 900 -->
@@ -33,8 +34,8 @@
<color name="bt_color_bg_6">#e9d2fd</color> <!-- Material Purple 100 -->
<color name="bt_color_bg_7">#cbf0f8</color> <!-- Material Cyan 100 -->
- <color name="dark_mode_icon_color_single_tone">#99000000</color>
- <color name="light_mode_icon_color_single_tone">#ffffff</color>
+ <color name="black">@*android:color/black</color>
+ <color name="white">@*android:color/white</color>
<color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color>
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 8bdbee3..9be3159 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -16,11 +16,15 @@
package com.android.settingslib.fuelgauge;
+import static android.provider.Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED;
+import static android.provider.Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED;
+
import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_STATE_MANUAL_UPDATE;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+import android.app.KeyguardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -33,6 +37,10 @@
import android.util.Log;
import android.util.Slog;
+import androidx.core.util.Function;
+
+import com.android.settingslib.flags.Flags;
+
/**
* Utilities related to battery saver.
*/
@@ -125,6 +133,19 @@
Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason);
}
final ContentResolver cr = context.getContentResolver();
+ final PowerManager powerManager = context.getSystemService(PowerManager.class);
+
+ if (Flags.extremePowerLowStateVulnerability()) {
+ var keyguardManager = context.getSystemService(KeyguardManager.class);
+ if (enable
+ && needFirstTimeWarning
+ && keyguardManager != null
+ && keyguardManager.isDeviceLocked()) {
+ var result = powerManager.setPowerSaveModeEnabled(true);
+ Log.d(TAG, "Device is locked, setPowerSaveModeEnabled by default. " + result);
+ return result;
+ }
+ }
final Bundle confirmationExtras = new Bundle(1);
confirmationExtras.putBoolean(EXTRA_CONFIRM_TEXT_ONLY, false);
@@ -136,7 +157,7 @@
setBatterySaverConfirmationAcknowledged(context);
}
- if (context.getSystemService(PowerManager.class).setPowerSaveModeEnabled(enable)) {
+ if (powerManager.setPowerSaveModeEnabled(enable)) {
if (enable) {
final int count =
Secure.getInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 0) + 1;
@@ -173,10 +194,7 @@
* @see #EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL
*/
public static boolean maybeShowBatterySaverConfirmation(Context context, Bundle extras) {
- if (Secure.getInt(context.getContentResolver(),
- Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 0) != 0
- && Secure.getInt(context.getContentResolver(),
- Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 0) != 0) {
+ if (isBatterySaverConfirmationHasBeenShowedBefore(context)) {
// Already shown.
return false;
}
@@ -184,6 +202,17 @@
return true;
}
+ /**
+ * Returns {@code true} if the battery saver confirmation warning has been acknowledged by the
+ * user in the past before.
+ */
+ public static boolean isBatterySaverConfirmationHasBeenShowedBefore(Context context) {
+ Function<String, Integer> secureGetInt =
+ key -> Secure.getInt(context.getContentResolver(), key, /* def= */ 0);
+ return secureGetInt.apply(LOW_POWER_WARNING_ACKNOWLEDGED) != 0
+ && secureGetInt.apply(EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED) != 0;
+ }
+
private static void recordBatterySaverEnabledReason(Context context, boolean enable,
@SaverManualEnabledReason int reason) {
final Bundle enabledReasonExtras = new Bundle(2);
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index ef0f6cb..6c2bd41 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -100,9 +100,9 @@
mCutoutHeightFraction = context.getResources().getFloat(
com.android.internal.R.dimen.config_signalCutoutHeightFraction);
mDarkModeFillColor = Utils.getColorStateListDefaultColor(context,
- R.color.dark_mode_icon_color_single_tone);
+ R.color.black);
mLightModeFillColor = Utils.getColorStateListDefaultColor(context,
- R.color.light_mode_icon_color_single_tone);
+ R.color.white);
mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
mTransparentPaint.setColor(context.getColor(android.R.color.transparent));
mTransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 58541418..273a63d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -21,6 +21,7 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.os.Handler
import com.android.settingslib.flags.Flags
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -50,6 +51,9 @@
private val notificationManager: NotificationManager,
val scope: CoroutineScope,
val backgroundCoroutineContext: CoroutineContext,
+ // This is nullable just to simplify testing, since SettingsLib doesn't have a good way
+ // to create a fake handler.
+ val backgroundHandler: Handler?,
) : ZenModeRepository {
private val notificationBroadcasts =
@@ -69,7 +73,14 @@
if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
addAction(
NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED)
- })
+ },
+ /* broadcastPermission = */ null,
+ /* scheduler = */ if (Flags.volumePanelBroadcastFix()) {
+ backgroundHandler
+ } else {
+ null
+ },
+ )
awaitClose { context.unregisterReceiver(receiver) }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index 80301c0..b143b22 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -28,23 +28,31 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
+import com.android.settingslib.flags.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import java.util.List;
@@ -54,26 +62,22 @@
private static final int BATTERY_SAVER_THRESHOLD_1 = 15;
private static final int BATTERY_SAVER_THRESHOLD_2 = 20;
- @Mock
- private Context mMockContext;
+ @Rule(order = 0) public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule(order = 1) public final MockitoRule mMockitoRule = MockitoJUnit.rule();
- @Mock
- private ContentResolver mMockResolver;
-
- @Mock
- private PowerManager mMockPowerManager;
+ @Mock private Context mMockContext;
+ @Mock private ContentResolver mMockResolver;
+ @Mock private PowerManager mMockPowerManager;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
when(mMockContext.getSystemService(eq(PowerManager.class))).thenReturn(mMockPowerManager);
when(mMockPowerManager.setPowerSaveModeEnabled(anyBoolean())).thenReturn(true);
}
@Test
- public void testSetPowerSaveMode_enableWithWarning_firstCall_needConfirmationWarning() {
+ public void setPowerSaveMode_enableWithWarning_firstCall_needConfirmationWarning() {
Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
@@ -96,7 +100,7 @@
}
@Test
- public void testSetPowerSaveMode_enableWithWarning_secondCall_expectUpdateIntent() {
+ public void setPowerSaveMode_enableWithWarning_secondCall_expectUpdateIntent() {
// Already acked.
Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
@@ -119,7 +123,7 @@
}
@Test
- public void testSetPowerSaveMode_enableWithWarning_thirdCall_expectUpdateIntent() {
+ public void setPowerSaveMode_enableWithWarning_thirdCall_expectUpdateIntent() {
// Already acked.
Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
@@ -142,7 +146,7 @@
}
@Test
- public void testSetPowerSaveMode_enableWithWarning_5thCall_needAutoSuggestionWarning() {
+ public void setPowerSaveMode_enableWithWarning_5thCall_needAutoSuggestionWarning() {
// Already acked.
Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
@@ -166,7 +170,7 @@
}
@Test
- public void testSetPowerSaveMode_enableWithoutWarning_expectUpdateIntent() {
+ public void setPowerSaveMode_enableWithoutWarning_expectUpdateIntent() {
Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
@@ -187,17 +191,17 @@
}
@Test
- public void testSetPowerSaveMode_disableWithoutWarning_expectUpdateIntent() {
+ public void setPowerSaveMode_disableWithoutWarning_expectUpdateIntent() {
verifyDisablePowerSaveMode(/* needFirstTimeWarning= */ false);
}
@Test
- public void testSetPowerSaveMode_disableWithWarning_expectUpdateIntent() {
+ public void setPowerSaveMode_disableWithWarning_expectUpdateIntent() {
verifyDisablePowerSaveMode(/* needFirstTimeWarning= */ true);
}
@Test
- public void testEnsureAutoBatterysaver_setNewPositiveValue_doNotOverwrite() {
+ public void ensureAutoBatterysaver_setNewPositiveValue_doNotOverwrite() {
Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
BatterySaverUtils.ensureAutoBatterySaver(mMockContext, BATTERY_SAVER_THRESHOLD_1);
@@ -212,7 +216,7 @@
}
@Test
- public void testSetAutoBatterySaverTriggerLevel_setSuppressSuggestion() {
+ public void setAutoBatterySaverTriggerLevel_setSuppressSuggestion() {
Global.putString(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, "null");
Secure.putString(mMockResolver, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, "null");
@@ -230,7 +234,7 @@
}
@Test
- public void testGetBatterySaverScheduleKey_returnExpectedKey() {
+ public void getBatterySaverScheduleKey_returnExpectedKey() {
Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
Global.putInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
@@ -253,8 +257,25 @@
KEY_NO_SCHEDULE);
}
+ @EnableFlags(Flags.FLAG_EXTREME_POWER_LOW_STATE_VULNERABILITY)
@Test
- public void testSetBatterySaverScheduleMode_setSchedule() {
+ public void setPowerSaveMode_1stTimeAndDeviceLocked_enableBatterySaver() {
+ var keyguardManager = mock(KeyguardManager.class);
+ when(mMockContext.getSystemService(KeyguardManager.class)).thenReturn(keyguardManager);
+ when(keyguardManager.isDeviceLocked()).thenReturn(true);
+ when(mMockPowerManager.setPowerSaveModeEnabled(true)).thenReturn(true);
+
+ var enableResult = BatterySaverUtils.setPowerSaveMode(
+ mMockContext,
+ /* enable= */ true,
+ /* needFirstTimeWarning= */ true,
+ /* reason= */ 0);
+
+ assertThat(enableResult).isTrue();
+ }
+
+ @Test
+ public void setBatterySaverScheduleMode_setSchedule() {
BatterySaverUtils.setBatterySaverScheduleMode(mMockContext, KEY_NO_SCHEDULE, -1);
assertThat(Global.getInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE, -1))
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 5294ce3..096c25d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -69,6 +69,7 @@
notificationManager,
testScope.backgroundScope,
testScope.testScheduler,
+ backgroundHandler = null,
)
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index d4a4703..5f23651 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -228,6 +228,7 @@
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED,
Settings.Secure.ACCESSIBILITY_PINCH_TO_ZOOM_ANYWHERE_ENABLED,
Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED,
Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 6df1c45..c8da8af 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -439,6 +439,7 @@
VALIDATORS.put(Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MANDATORY_BIOMETRICS, new InclusiveIntegerRangeValidator(0, 1));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index e77cf2f..2227943 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -189,22 +189,13 @@
public static HashMap<String, String> getAllFlags(IContentProvider provider) {
HashMap<String, String> allFlags = new HashMap<String, String>();
- try {
- Bundle args = new Bundle();
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- Bundle b = provider.call(new AttributionSource(Process.myUid(),
- resolveCallingPackage(), null), Settings.AUTHORITY,
- Settings.CALL_METHOD_LIST_CONFIG, null, args);
- if (b != null) {
- Map<String, String> flagsToValues =
- (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
- allFlags.putAll(flagsToValues);
+ for (DeviceConfig.Properties properties : DeviceConfig.getAllProperties()) {
+ List<String> keys = new ArrayList<>(properties.getKeyset());
+ for (String flagName : properties.getKeyset()) {
+ String fullName = properties.getNamespace() + "/" + flagName;
+ allFlags.put(fullName, properties.getString(flagName, null));
}
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
}
-
return allFlags;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 54f0d7a..e8ef620 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -44,6 +44,7 @@
import static com.android.providers.settings.SettingsState.makeKey;
import android.Manifest;
+import android.aconfigd.AconfigdFlagInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -1371,10 +1372,27 @@
}
}
+ Map<String, AconfigdFlagInfo> aconfigFlagInfos =
+ settingsState.getAconfigDefaultFlags();
+
for (int i = 0; i < nameCount; i++) {
String name = names.get(i);
Setting setting = settingsState.getSettingLocked(name);
- if (prefix == null || setting.getName().startsWith(prefix)) {
+ if (prefix == null || name.startsWith(prefix)) {
+ if (Flags.ignoreXmlForReadOnlyFlags()) {
+ int slashIndex = name.indexOf("/");
+ boolean validSlashIndex = slashIndex != -1
+ && slashIndex != 0
+ && slashIndex != name.length();
+ if (validSlashIndex) {
+ String flagName = name.substring(slashIndex + 1);
+ AconfigdFlagInfo flagInfo = aconfigFlagInfos.get(flagName);
+ if (flagInfo != null && !flagInfo.getIsReadWrite()) {
+ continue;
+ }
+ }
+ }
+
flagsToValues.put(setting.getName(), setting.getValue());
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index fd4fc20..452edd9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -774,6 +774,13 @@
}
}
+ @NonNull
+ public Map<String, AconfigdFlagInfo> getAconfigDefaultFlags() {
+ synchronized (mLock) {
+ return mAconfigDefaultFlags;
+ }
+ }
+
// The settings provider must hold its lock when calling here.
public Setting getSettingLocked(String name) {
if (TextUtils.isEmpty(name)) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index d20fbf5..4f5955b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -41,3 +41,14 @@
description: "If this flag is detected as true on boot, writes a logfile to track storage migration correctness."
bug: "328444881"
}
+
+flag {
+ name: "ignore_xml_for_read_only_flags"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, ignore any flag in the SettingsProvider XML for RO flags."
+ bug: "345007098"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 469b9ce..9cbb1bd 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -555,6 +555,7 @@
"androidx.exifinterface_exifinterface",
"androidx.room_room-runtime",
"androidx.room_room-ktx",
+ "androidx.datastore_datastore-preferences",
"com.google.android.material_material",
"device_state_flags_lib",
"kotlinx_coroutines_android",
@@ -708,6 +709,7 @@
"androidx.exifinterface_exifinterface",
"androidx.room_room-runtime",
"androidx.room_room-ktx",
+ "androidx.datastore_datastore-preferences",
"device_state_flags_lib",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3f165a3..e2ecda3 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -591,13 +591,6 @@
}
flag {
- name: "screenshot_shelf_ui2"
- namespace: "systemui"
- description: "Use new shelf UI flow for screenshots"
- bug: "329659738"
-}
-
-flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -824,13 +817,6 @@
}
flag {
- name: "media_controls_refactor"
- namespace: "systemui"
- description: "Refactors media code to follow the recommended architecture"
- bug: "326408371"
-}
-
-flag {
name: "qs_tile_focus_state"
namespace: "systemui"
description: "enables new focus outline for qs tiles when focused on with physical keyboard"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index d046631..43d51c3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -28,7 +28,6 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.CommunalSwipeDetector
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index b7d6e09..f6535ec0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -661,10 +661,7 @@
val addWidgetText = stringResource(R.string.hub_mode_add_widget_button_text)
ToolbarButton(
isPrimary = !removeEnabled,
- modifier =
- Modifier.align(Alignment.CenterStart).semantics {
- contentDescription = addWidgetText
- },
+ modifier = Modifier.align(Alignment.CenterStart),
onClick = onOpenWidgetPicker,
) {
Icon(Icons.Default.Add, null)
@@ -701,7 +698,7 @@
),
verticalAlignment = Alignment.CenterVertically
) {
- Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget))
+ Icon(Icons.Default.Close, contentDescription = null)
Text(
text = stringResource(R.string.button_to_remove_widget),
)
@@ -714,10 +711,7 @@
modifier = Modifier.align(Alignment.CenterEnd),
onClick = onEditDone,
) {
- Icon(
- Icons.Default.Check,
- stringResource(id = R.string.hub_mode_editing_exit_button_text)
- )
+ Icon(Icons.Default.Check, contentDescription = null)
Text(
text = stringResource(R.string.hub_mode_editing_exit_button_text),
)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt
similarity index 84%
rename from packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt
rename to packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt
index 7be34ca..3fda9b8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.compose.animation.scene
+package com.android.systemui.communal.ui.compose
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.pointer.PointerInputChange
@@ -22,10 +22,12 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SwipeDetector
+import com.android.compose.animation.scene.SwipeSource
+import com.android.compose.animation.scene.SwipeSourceDetector
import kotlin.math.abs
-private const val TRAVEL_RATIO_THRESHOLD = .5f
-
/**
* {@link CommunalSwipeDetector} provides an implementation of {@link SwipeDetector} and {@link
* SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable
@@ -33,6 +35,10 @@
*/
class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) :
SwipeSourceDetector, SwipeDetector {
+ companion object {
+ private const val TRAVEL_RATIO_THRESHOLD = .5f
+ }
+
override fun source(
layoutSize: IntSize,
position: IntOffset,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
new file mode 100644
index 0000000..e4916b1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 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 com.android.systemui.communal.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.devicePolicyManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.os.UserManager
+import android.os.userManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalSettingsInteractorTest : SysuiTestCase() {
+
+ private lateinit var userManager: UserManager
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var userTracker: FakeUserTracker
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: CommunalSettingsInteractor
+
+ @Before
+ fun setUp() {
+ userManager = kosmos.userManager
+ userRepository = kosmos.fakeUserRepository
+ userTracker = kosmos.fakeUserTracker
+
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+
+ underTest = kosmos.communalSettingsInteractor
+ }
+
+ @Test
+ fun filterUsers_dontFilteredUsersWhenAllAreAllowed() =
+ testScope.runTest {
+ // If no users have any keyguard features disabled...
+ val disallowedUser by
+ collectLastValue(underTest.workProfileUserDisallowedByDevicePolicy)
+ // ...then the disallowed user should be null
+ assertNull(disallowedUser)
+ }
+
+ @Test
+ fun filterUsers_filterWorkProfileUserWhenDisallowed() =
+ testScope.runTest {
+ // If the work profile user has keyguard widgets disabled...
+ setKeyguardFeaturesDisabled(
+ USER_INFO_WORK,
+ DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+ )
+ // ...then the disallowed user match the work profile
+ val disallowedUser by
+ collectLastValue(underTest.workProfileUserDisallowedByDevicePolicy)
+ assertNotNull(disallowedUser)
+ assertEquals(USER_INFO_WORK.id, disallowedUser!!.id)
+ }
+
+ private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
+ whenever(
+ kosmos.devicePolicyManager.getKeyguardDisabledFeatures(
+ anyOrNull(),
+ ArgumentMatchers.eq(user.id)
+ )
+ )
+ .thenReturn(disabledFlags)
+ kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ )
+ }
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ val USER_INFO_WORK =
+ UserInfo(
+ 10,
+ "work",
+ /* iconPath= */ "",
+ /* flags= */ 0,
+ UserManager.USER_TYPE_PROFILE_MANAGED,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
new file mode 100644
index 0000000..4a5342a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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 com.android.systemui.education.data.repository
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.education.GestureType.BACK_GESTURE
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ContextualEducationRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: ContextualEducationRepository
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val dsScopeProvider: Provider<CoroutineScope> = Provider {
+ TestScope(kosmos.testDispatcher).backgroundScope
+ }
+ private val testUserId = 1111
+
+ // For deleting any test files created after the test
+ @get:Rule val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
+
+ @Before
+ fun setUp() {
+ // Create TestContext here because TemporaryFolder.create() is called in @Before. It is
+ // needed before calling TemporaryFolder.newFolder().
+ val testContext = TestContext(context, tmpFolder.newFolder())
+ val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider)
+ underTest = ContextualEducationRepository(userRepository)
+ underTest.setUser(testUserId)
+ }
+
+ @Test
+ fun changeRetrievedValueForNewUser() =
+ testScope.runTest {
+ // Update data for old user.
+ underTest.incrementSignalCount(BACK_GESTURE)
+ val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE))
+ assertThat(model?.signalCount).isEqualTo(1)
+
+ // User is changed.
+ underTest.setUser(1112)
+ // Assert count is 0 after user is changed.
+ assertThat(model?.signalCount).isEqualTo(0)
+ }
+
+ @Test
+ fun incrementSignalCount() =
+ testScope.runTest {
+ underTest.incrementSignalCount(BACK_GESTURE)
+ val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE))
+ assertThat(model?.signalCount).isEqualTo(1)
+ }
+
+ /** Test context which allows overriding getFilesDir path */
+ private class TestContext(context: Context, private val folder: File) :
+ SysuiTestableContext(context) {
+ override fun getFilesDir(): File {
+ return folder
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index fd1b213..b5e47d1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -85,6 +85,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -181,6 +182,7 @@
kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
buildNotificationRows(isPinned = false)
+ advanceTimeBy(50L) // account for HeadsUpNotificationInteractor debounce
assertThat(isVisible).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 8b4265f..5ef3485 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -23,6 +23,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -279,6 +280,64 @@
}
@Test
+ fun isHeadsUpOrAnimatingAway_falseOnStart() =
+ testScope.runTest {
+ val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
+
+ runCurrent()
+
+ assertThat(isHeadsUpOrAnimatingAway).isFalse()
+ }
+
+ @Test
+ fun isHeadsUpOrAnimatingAway_hasPinnedRows() =
+ testScope.runTest {
+ val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
+
+ // WHEN a row is pinned
+ headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+ runCurrent()
+
+ assertThat(isHeadsUpOrAnimatingAway).isTrue()
+ }
+
+ @Test
+ fun isHeadsUpOrAnimatingAway_headsUpAnimatingAway() =
+ testScope.runTest {
+ val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
+
+ // WHEN the last row is animating away
+ headsUpRepository.setHeadsUpAnimatingAway(true)
+ runCurrent()
+
+ assertThat(isHeadsUpOrAnimatingAway).isTrue()
+ }
+
+ @Test
+ fun isHeadsUpOrAnimatingAway_headsUpAnimatingAwayDebounced() =
+ testScope.runTest {
+ val values by collectValues(underTest.isHeadsUpOrAnimatingAway)
+
+ // GIVEN a row is pinned
+ headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+ runCurrent()
+ assertThat(values.size).isEqualTo(2)
+ assertThat(values.first()).isFalse() // initial value
+ assertThat(values.last()).isTrue()
+
+ // WHEN the last row is removed
+ headsUpRepository.setNotifications(emptyList())
+ runCurrent()
+ // AND starts to animate away
+ headsUpRepository.setHeadsUpAnimatingAway(true)
+ runCurrent()
+
+ // THEN isHeadsUpOrAnimatingAway remained true
+ assertThat(values.size).isEqualTo(2)
+ assertThat(values.last()).isTrue()
+ }
+
+ @Test
fun showHeadsUpStatusBar_true() =
testScope.runTest {
val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.kt
new file mode 100644
index 0000000..1b704b4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 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 com.android.systemui.util.service
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.service.ObservableServiceConnection.DISCONNECT_REASON_DISCONNECTED
+import com.android.systemui.util.time.fakeSystemClock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PersistentConnectionManagerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val fakeClock = kosmos.fakeSystemClock
+ private val fakeExecutor = kosmos.fakeExecutor
+
+ private class Proxy {
+ // Fake proxy class
+ }
+
+ private val connection: ObservableServiceConnection<Proxy> = mock()
+ private val observer: Observer = mock()
+
+ private val underTest: PersistentConnectionManager<Proxy> by lazy {
+ PersistentConnectionManager(
+ /* clock = */ fakeClock,
+ /* bgExecutor = */ fakeExecutor,
+ /* dumpManager = */ kosmos.dumpManager,
+ /* dumpsysName = */ DUMPSYS_NAME,
+ /* serviceConnection = */ connection,
+ /* maxReconnectAttempts = */ MAX_RETRIES,
+ /* baseReconnectDelayMs = */ RETRY_DELAY_MS,
+ /* minConnectionDurationMs = */ CONNECTION_MIN_DURATION_MS,
+ /* observer = */ observer
+ )
+ }
+
+ /** Validates initial connection. */
+ @Test
+ fun testConnect() {
+ underTest.start()
+ captureCallbackAndVerifyBind(connection).onConnected(connection, mock<Proxy>())
+ }
+
+ /** Ensures reconnection on disconnect. */
+ @Test
+ fun testExponentialRetryOnDisconnect() {
+ underTest.start()
+
+ // IF service is connected...
+ val captor = argumentCaptor<ObservableServiceConnection.Callback<Proxy>>()
+ verify(connection, times(1)).bind()
+ verify(connection).addCallback(captor.capture())
+ val callback = captor.lastValue
+ callback.onConnected(connection, mock<Proxy>())
+
+ // ...AND service becomes disconnected within CONNECTION_MIN_DURATION_MS
+ callback.onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+
+ // THEN verify we retry to bind after the retry delay. (RETRY #1)
+ verify(connection, times(1)).bind()
+ fakeClock.advanceTime(RETRY_DELAY_MS.toLong())
+ verify(connection, times(2)).bind()
+
+ // IF service becomes disconnected for a second time after first retry...
+ callback.onConnected(connection, mock<Proxy>())
+ callback.onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+
+ // THEN verify we retry after a longer delay of 2 * RETRY_DELAY_MS (RETRY #2)
+ fakeClock.advanceTime(RETRY_DELAY_MS.toLong())
+ verify(connection, times(2)).bind()
+ fakeClock.advanceTime(RETRY_DELAY_MS.toLong())
+ verify(connection, times(3)).bind()
+
+ // IF service becomes disconnected for a third time after the second retry...
+ callback.onConnected(connection, mock<Proxy>())
+ callback.onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+
+ // THEN verify we retry after a longer delay of 4 * RETRY_DELAY_MS (RETRY #3)
+ fakeClock.advanceTime(3 * RETRY_DELAY_MS.toLong())
+ verify(connection, times(3)).bind()
+ fakeClock.advanceTime(RETRY_DELAY_MS.toLong())
+ verify(connection, times(4)).bind()
+ }
+
+ @Test
+ fun testDoesNotRetryAfterMaxRetries() {
+ underTest.start()
+
+ val captor = argumentCaptor<ObservableServiceConnection.Callback<Proxy>>()
+ verify(connection).addCallback(captor.capture())
+ val callback = captor.lastValue
+
+ // IF we retry MAX_TRIES times...
+ for (attemptCount in 0 until MAX_RETRIES + 1) {
+ verify(connection, times(attemptCount + 1)).bind()
+ callback.onConnected(connection, mock<Proxy>())
+ callback.onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+ fakeClock.advanceTime(Math.scalb(RETRY_DELAY_MS.toDouble(), attemptCount).toLong())
+ }
+
+ // THEN we should not retry again after the last attempt.
+ fakeExecutor.advanceClockToLast()
+ verify(connection, times(MAX_RETRIES + 1)).bind()
+ }
+
+ @Test
+ fun testEnsureNoRetryIfServiceNeverConnectsAfterRetry() {
+ underTest.start()
+
+ with(captureCallbackAndVerifyBind(connection)) {
+ // IF service initially connects and then disconnects...
+ onConnected(connection, mock<Proxy>())
+ onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+ fakeExecutor.advanceClockToLast()
+ fakeExecutor.runAllReady()
+
+ // ...AND we retry once.
+ verify(connection, times(1)).bind()
+
+ // ...AND service disconnects after initial retry without ever connecting again.
+ onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+ fakeExecutor.advanceClockToLast()
+ fakeExecutor.runAllReady()
+
+ // THEN verify another retry is not triggered.
+ verify(connection, times(1)).bind()
+ }
+ }
+
+ @Test
+ fun testEnsureNoRetryIfServiceNeverInitiallyConnects() {
+ underTest.start()
+
+ with(captureCallbackAndVerifyBind(connection)) {
+ // IF service never connects and we just receive the disconnect signal...
+ onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+ fakeExecutor.advanceClockToLast()
+ fakeExecutor.runAllReady()
+
+ // THEN do not retry
+ verify(connection, never()).bind()
+ }
+ }
+
+ /** Ensures manual unbind does not reconnect. */
+ @Test
+ fun testStopDoesNotReconnect() {
+ underTest.start()
+
+ val connectionCallbackCaptor = argumentCaptor<ObservableServiceConnection.Callback<Proxy>>()
+ verify(connection).addCallback(connectionCallbackCaptor.capture())
+ verify(connection).bind()
+ clearInvocations(connection)
+
+ underTest.stop()
+ fakeExecutor.advanceClockToNext()
+ fakeExecutor.runAllReady()
+ verify(connection, never()).bind()
+ }
+
+ /** Ensures rebind on package change. */
+ @Test
+ fun testAttemptOnPackageChange() {
+ underTest.start()
+
+ verify(connection).bind()
+
+ val callbackCaptor = argumentCaptor<Observer.Callback>()
+ captureCallbackAndVerifyBind(connection).onConnected(connection, mock<Proxy>())
+
+ verify(observer).addCallback(callbackCaptor.capture())
+ callbackCaptor.lastValue.onSourceChanged()
+ verify(connection).bind()
+ }
+
+ @Test
+ fun testAddConnectionCallback() {
+ val connectionCallback: ObservableServiceConnection.Callback<Proxy> = mock()
+ underTest.addConnectionCallback(connectionCallback)
+ verify(connection).addCallback(connectionCallback)
+ }
+
+ @Test
+ fun testRemoveConnectionCallback() {
+ val connectionCallback: ObservableServiceConnection.Callback<Proxy> = mock()
+ underTest.removeConnectionCallback(connectionCallback)
+ verify(connection).removeCallback(connectionCallback)
+ }
+
+ /** Helper method to capture the [ObservableServiceConnection.Callback] */
+ private fun captureCallbackAndVerifyBind(
+ mConnection: ObservableServiceConnection<Proxy>,
+ ): ObservableServiceConnection.Callback<Proxy> {
+
+ val connectionCallbackCaptor = argumentCaptor<ObservableServiceConnection.Callback<Proxy>>()
+ verify(mConnection).addCallback(connectionCallbackCaptor.capture())
+ verify(mConnection).bind()
+ clearInvocations(mConnection)
+
+ return connectionCallbackCaptor.lastValue
+ }
+
+ companion object {
+ private const val MAX_RETRIES = 3
+ private const val RETRY_DELAY_MS = 1000
+ private const val CONNECTION_MIN_DURATION_MS = 5000
+ private const val DUMPSYS_NAME = "dumpsys_name"
+ }
+}
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 2500769..65005f8 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -24,18 +24,29 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/clipboard_overlay_window_name">
- <ImageView
+ <!-- Min edge spacing guideline off of which the preview and actions can be anchored (without
+ this we'd need to express margins as the sum of two different dimens). -->
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/min_edge_guideline"
+ app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
+ android:orientation="vertical"/>
+ <!-- Negative horizontal margin because this container background must render beyond the thing
+ it's constrained by (the actions themselves). -->
+ <FrameLayout
android:id="@+id/actions_container_background"
android:visibility="gone"
android:layout_height="0dp"
android:layout_width="0dp"
android:elevation="4dp"
- android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:background="@drawable/shelf_action_chip_container_background"
+ android:layout_marginStart="@dimen/negative_overlay_action_container_minimum_edge_spacing"
+ android:layout_marginEnd="@dimen/negative_overlay_action_container_minimum_edge_spacing"
android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"
+ app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
+ app:layout_constraintTop_toTopOf="@id/actions_container"
+ app:layout_constraintEnd_toEndOf="@id/actions_container"
app:layout_constraintBottom_toBottomOf="parent"/>
<HorizontalScrollView
android:id="@+id/actions_container"
@@ -56,10 +67,13 @@
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:paddingStart="@dimen/shelf_action_chip_margin_start"
+ android:showDividers="middle"
+ android:divider="@drawable/shelf_action_chip_divider"
android:animateLayoutChanges="true">
- <include layout="@layout/overlay_action_chip"
+ <include layout="@layout/shelf_action_chip"
android:id="@+id/share_chip"/>
- <include layout="@layout/overlay_action_chip"
+ <include layout="@layout/shelf_action_chip"
android:id="@+id/remote_copy_chip"/>
</LinearLayout>
</HorizontalScrollView>
@@ -73,7 +87,7 @@
android:layout_marginBottom="@dimen/overlay_preview_container_margin"
android:elevation="7dp"
android:background="@drawable/overlay_border"
- app:layout_constraintStart_toStartOf="@id/actions_container_background"
+ app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
app:layout_constraintTop_toTopOf="@id/clipboard_preview"
app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay2.xml b/packages/SystemUI/res/layout/clipboard_overlay2.xml
deleted file mode 100644
index 65005f8..0000000
--- a/packages/SystemUI/res/layout/clipboard_overlay2.xml
+++ /dev/null
@@ -1,202 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 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.
- -->
-<com.android.systemui.clipboardoverlay.ClipboardOverlayView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/clipboard_ui"
- android:theme="@style/FloatingOverlay"
- android:alpha="0"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:contentDescription="@string/clipboard_overlay_window_name">
- <!-- Min edge spacing guideline off of which the preview and actions can be anchored (without
- this we'd need to express margins as the sum of two different dimens). -->
- <androidx.constraintlayout.widget.Guideline
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/min_edge_guideline"
- app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
- android:orientation="vertical"/>
- <!-- Negative horizontal margin because this container background must render beyond the thing
- it's constrained by (the actions themselves). -->
- <FrameLayout
- android:id="@+id/actions_container_background"
- android:visibility="gone"
- android:layout_height="0dp"
- android:layout_width="0dp"
- android:elevation="4dp"
- android:background="@drawable/shelf_action_chip_container_background"
- android:layout_marginStart="@dimen/negative_overlay_action_container_minimum_edge_spacing"
- android:layout_marginEnd="@dimen/negative_overlay_action_container_minimum_edge_spacing"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
- app:layout_constraintTop_toTopOf="@id/actions_container"
- app:layout_constraintEnd_toEndOf="@id/actions_container"
- app:layout_constraintBottom_toBottomOf="parent"/>
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:paddingEnd="@dimen/overlay_action_container_padding_end"
- android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
- android:elevation="4dp"
- android:scrollbars="none"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintWidth_percent="1.0"
- app:layout_constraintWidth_max="wrap"
- app:layout_constraintStart_toEndOf="@+id/preview_border"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
- <LinearLayout
- android:id="@+id/actions"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingStart="@dimen/shelf_action_chip_margin_start"
- android:showDividers="middle"
- android:divider="@drawable/shelf_action_chip_divider"
- android:animateLayoutChanges="true">
- <include layout="@layout/shelf_action_chip"
- android:id="@+id/share_chip"/>
- <include layout="@layout/shelf_action_chip"
- android:id="@+id/remote_copy_chip"/>
- </LinearLayout>
- </HorizontalScrollView>
- <View
- android:id="@+id/preview_border"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_preview_container_margin"
- android:layout_marginTop="@dimen/overlay_border_width_neg"
- android:layout_marginEnd="@dimen/overlay_border_width_neg"
- android:layout_marginBottom="@dimen/overlay_preview_container_margin"
- android:elevation="7dp"
- android:background="@drawable/overlay_border"
- app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
- app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
- <FrameLayout
- android:id="@+id/clipboard_preview"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/overlay_border_width"
- android:layout_marginBottom="@dimen/overlay_border_width"
- android:layout_gravity="center"
- android:elevation="7dp"
- android:background="@drawable/overlay_preview_background"
- android:clipChildren="true"
- android:clipToOutline="true"
- android:clipToPadding="true"
- app:layout_constraintStart_toStartOf="@id/preview_border"
- app:layout_constraintBottom_toBottomOf="@id/preview_border">
- <TextView android:id="@+id/text_preview"
- android:textFontWeight="500"
- android:padding="8dp"
- android:gravity="center|start"
- android:ellipsize="end"
- android:autoSizeTextType="uniform"
- android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
- android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
- android:textColor="?attr/overlayButtonTextColor"
- android:textColorLink="?attr/overlayButtonTextColor"
- android:background="?androidprv:attr/colorAccentSecondary"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="@dimen/clipboard_preview_size"/>
- <ImageView
- android:id="@+id/image_preview"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true"
- android:contentDescription="@string/clipboard_image_preview"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/hidden_preview"
- android:visibility="gone"
- android:textFontWeight="500"
- android:padding="8dp"
- android:gravity="center"
- android:textSize="14sp"
- android:textColor="?attr/overlayButtonTextColor"
- android:background="?androidprv:attr/colorAccentSecondary"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="@dimen/clipboard_preview_size"/>
- </FrameLayout>
- <LinearLayout
- android:id="@+id/minimized_preview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:elevation="7dp"
- android:padding="8dp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- android:background="@drawable/clipboard_minimized_background">
- <ImageView
- android:src="@drawable/ic_content_paste"
- android:tint="?attr/overlayButtonTextColor"
- android:layout_width="24dp"
- android:layout_height="24dp"/>
- <ImageView
- android:src="@*android:drawable/ic_chevron_end"
- android:tint="?attr/overlayButtonTextColor"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:paddingEnd="-8dp"
- android:paddingStart="-4dp"/>
- </LinearLayout>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_content_top"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- app:barrierDirection="top"
- app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_content_end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- app:barrierDirection="end"
- app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
- <FrameLayout
- android:id="@+id/dismiss_button"
- android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
- android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- android:elevation="10dp"
- android:visibility="gone"
- android:alpha="0"
- app:layout_constraintStart_toEndOf="@id/clipboard_content_end"
- app:layout_constraintEnd_toEndOf="@id/clipboard_content_end"
- app:layout_constraintTop_toTopOf="@id/clipboard_content_top"
- app:layout_constraintBottom_toTopOf="@id/clipboard_content_top"
- android:contentDescription="@string/clipboard_dismiss_description">
- <ImageView
- android:id="@+id/dismiss_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
- android:background="@drawable/circular_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimaryFixedDim"
- android:tint="?androidprv:attr/materialColorOnPrimaryFixed"
- android:padding="4dp"
- android:src="@drawable/ic_close"/>
- </FrameLayout>
-</com.android.systemui.clipboardoverlay.ClipboardOverlayView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index 2616e8a..aa083ad 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -46,7 +46,7 @@
android:layout_marginTop="@dimen/screenrecord_title_margin_top"
android:gravity="center"/>
<Spinner
- android:id="@+id/screen_share_mode_spinner"
+ android:id="@+id/screen_share_mode_options"
android:layout_width="match_parent"
android:layout_height="@dimen/screenrecord_spinner_height"
android:layout_marginTop="@dimen/screenrecord_spinner_margin"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0350cd7..ca55c23 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -100,8 +100,8 @@
<!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
<color name="navigation_bar_icon_color">#E5FFFFFF</color>
- <color name="navigation_bar_home_handle_light_color">#EBffffff</color>
- <color name="navigation_bar_home_handle_dark_color">#99000000</color>
+ <color name="white">@*android:color/white</color>
+ <color name="black">@*android:color/black</color>
<!-- The shadow color for light navigation bar icons. -->
<color name="nav_key_button_shadow_color">#30000000</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 80b9ec7..84d5dcb 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -56,7 +56,7 @@
enabled for OLED devices to reduce/prevent burn in on the navigation bar (because of the
black background and static button placements) and disabled for all other devices to
prevent wasting cpu cycles on the dimming animation -->
- <bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">true</bool>
+ <bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">false</bool>
<!-- The maximum number of tiles in the QuickQSPanel -->
<integer name="quick_qs_panel_max_tiles">4</integer>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0bc2c82..dee5528 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -321,11 +321,11 @@
<!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
<string name="screenrecord_start_error">Error starting screen recording</string>
<!-- Title for a dialog shown to the user that will let them stop recording their screen [CHAR LIMIT=50] -->
- <string name="screenrecord_stop_dialog_title">Stop recording screen?</string>
- <!-- Text telling a user that they will stop recording their screen if they click the "Stop recording" button [CHAR LIMIT=100] -->
- <string name="screenrecord_stop_dialog_message">You will stop recording your screen</string>
- <!-- Text telling a user that they will stop recording the contents of the specified [app_name] if they click the "Stop recording" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
- <string name="screenrecord_stop_dialog_message_specific_app">You will stop recording <b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g></b></string>
+ <string name="screenrecord_stop_dialog_title">Stop recording?</string>
+ <!-- Text telling a user that they're currently recording their screen [CHAR LIMIT=100] -->
+ <string name="screenrecord_stop_dialog_message">You\'re currently recording your entire screen</string>
+ <!-- Text telling a user that they're currently recording the contents of the specified [app_name]. [CHAR LIMIT=100] -->
+ <string name="screenrecord_stop_dialog_message_specific_app">You\'re currently recording <xliff:g id="app_name" example="Photos App">%1$s</xliff:g></string>
<!-- Button to stop a screen recording [CHAR LIMIT=35] -->
<string name="screenrecord_stop_dialog_button">Stop recording</string>
@@ -333,25 +333,33 @@
<string name="share_to_app_chip_accessibility_label">Sharing screen</string>
<!-- Title for a dialog shown to the user that will let them stop sharing their screen to another app on the device [CHAR LIMIT=50] -->
<string name="share_to_app_stop_dialog_title">Stop sharing screen?</string>
- <!-- Text telling a user that they will stop sharing their screen if they click the "Stop sharing" button [CHAR LIMIT=100] -->
- <string name="share_to_app_stop_dialog_message">You will stop sharing your screen</string>
- <!-- Text telling a user that they will stop sharing the contents of the specified [app_name] if they click the "Stop sharing" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
- <string name="share_to_app_stop_dialog_message_specific_app">You will stop sharing <b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g></b></string>
+ <!-- Text telling a user that they're currently sharing their entire screen to [host_app_name] (i.e. [host_app_name] can currently see all screen content) [CHAR LIMIT=150] -->
+ <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app">You\'re currently sharing your entire screen with <xliff:g id="host_app_name" example="Screen Recorder App">%1$s</xliff:g></string>
+ <!-- Text telling a user that they're currently sharing their entire screen to an app (but we don't know what app) [CHAR LIMIT=150] -->
+ <string name="share_to_app_stop_dialog_message_entire_screen">You\'re currently sharing your entire screen with an app</string>
+ <!-- Text telling a user that they're currently sharing the contents of [app_being_shared_name]. (i.e. some app can currently see the content of [app_being_shared_name]). [CHAR LIMIT=150] -->
+ <string name="share_to_app_stop_dialog_message_single_app_specific">You\'re currently sharing <xliff:g id="app_being_shared_name" example="Photos App">%1$s</xliff:g></string>
+ <!-- Text telling a user that they're currently sharing their screen [CHAR LIMIT=150] -->
+ <string name="share_to_app_stop_dialog_message_single_app_generic">You\'re currently sharing an app</string>
<!-- Button to stop screen sharing [CHAR LIMIT=35] -->
<string name="share_to_app_stop_dialog_button">Stop sharing</string>
<!-- Content description for the status bar chip shown to the user when they're casting their screen to a different device [CHAR LIMIT=NONE] -->
<string name="cast_screen_to_other_device_chip_accessibility_label">Casting screen</string>
- <!-- Title for a dialog shown to the user that will let them stop casting their screen to a different device [CHAR LIMIT=50] -->
- <string name="cast_screen_to_other_device_stop_dialog_title">Stop casting screen?</string>
<!-- Title for a dialog shown to the user that will let them stop casting to a different device [CHAR LIMIT=50] -->
<string name="cast_to_other_device_stop_dialog_title">Stop casting?</string>
- <!-- Text telling a user that they will stop casting their screen to a different device if they click the "Stop casting" button [CHAR LIMIT=100] -->
- <string name="cast_screen_to_other_device_stop_dialog_message">You will stop casting your screen</string>
- <!-- Text telling a user that they will stop casting the contents of the specified [app_name] to a different device if they click the "Stop casting" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
- <string name="cast_screen_to_other_device_stop_dialog_message_specific_app">You will stop casting <b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g></b></string>
- <!-- Text telling a user that they're currently casting to a different device [CHAR LIMIT=100] -->
- <string name="cast_to_other_device_stop_dialog_message">You\'re currently casting</string>
+ <!-- Text telling a user that they're currently casting their screen to a different device. The device receiving the cast is named [device_name]. [CHAR LIMIT=150] -->
+ <string name="cast_to_other_device_stop_dialog_message_entire_screen_with_device">You\'re currently casting your entire screen to <xliff:g id="device_name" example="Living Room Device">%1$s</xliff:g></string>
+ <!-- Text telling a user that they're currently casting their screen to a nearby device. [CHAR LIMIT=150] -->
+ <string name="cast_to_other_device_stop_dialog_message_entire_screen">You\'re currently casting your entire screen to a nearby device</string>
+ <!-- Text telling a user that they're currently casting the contents of [app_being_shared_name] to a different device. The device receiving the cast is named [device_name]. [CHAR LIMIT=150] -->
+ <string name="cast_to_other_device_stop_dialog_message_specific_app_with_device">You\'re currently casting <xliff:g id="app_being_shared_name" example="Photos App">%1$s</xliff:g> to <xliff:g id="device_name" example="Living Room Device">%2$s</xliff:g></string>
+ <!-- Text telling a user that they're currently casting the contents of [app_being_shared_name] to a different device. [CHAR LIMIT=150] -->
+ <string name="cast_to_other_device_stop_dialog_message_specific_app">You\'re currently casting <xliff:g id="app_being_shared_name" example="Photos App">%1$s</xliff:g> to a nearby device</string>
+ <!-- Text telling a user that they're currently casting to a different device. The device receiving the cast is named [device_name]. [CHAR LIMIT=100] -->
+ <string name="cast_to_other_device_stop_dialog_message_generic_with_device">You\'re currently casting to <xliff:g id="device_name" example="Living Room Device">%1$s</xliff:g></string>
+ <!-- Text telling a user that they're currently casting to a nearby device [CHAR LIMIT=100] -->
+ <string name="cast_to_other_device_stop_dialog_message_generic">You\'re currently casting to a nearby device</string>
<!-- Button to stop screen casting to a different device [CHAR LIMIT=35] -->
<string name="cast_to_other_device_stop_dialog_button">Stop casting</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7475eb2..047578c 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -622,14 +622,14 @@
<style name="DualToneLightTheme">
<item name="iconBackgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
<item name="fillColor">@color/light_mode_icon_color_dual_tone_fill</item>
- <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
- <item name="homeHandleColor">@color/navigation_bar_home_handle_light_color</item>
+ <item name="singleToneColor">@color/white</item>
+ <item name="homeHandleColor">@color/white</item>
</style>
<style name="DualToneDarkTheme">
<item name="iconBackgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
<item name="fillColor">@color/dark_mode_icon_color_dual_tone_fill</item>
- <item name="singleToneColor">@color/dark_mode_icon_color_single_tone</item>
- <item name="homeHandleColor">@color/navigation_bar_home_handle_dark_color</item>
+ <item name="singleToneColor">@color/black</item>
+ <item name="homeHandleColor">@color/black</item>
</style>
<style name="QSHeaderDarkTheme">
<item name="iconBackgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
@@ -648,7 +648,7 @@
<item name="singleToneColor">?android:attr/textColorPrimary</item>
</style>
<style name="ScreenPinningRequestTheme" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent">
- <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
+ <item name="singleToneColor">@color/white</item>
</style>
<style name="TextAppearance.Volume">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/education/GestureType.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/education/GestureType.kt
new file mode 100644
index 0000000..9a5c77a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/education/GestureType.kt
@@ -0,0 +1,21 @@
+/*
+ * 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 com.android.systemui.shared.education
+
+enum class GestureType {
+ BACK_GESTURE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index ba236ba..1762d82 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,8 +18,6 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static com.android.systemui.Flags.screenshotShelfUi2;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -61,7 +59,6 @@
import com.android.systemui.res.R;
import com.android.systemui.screenshot.DraggableConstraintLayout;
import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.OverlayActionChip;
import com.android.systemui.screenshot.ui.binder.ActionButtonViewBinder;
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance;
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel;
@@ -152,66 +149,47 @@
}
private void bindDefaultActionChips() {
- if (screenshotShelfUi2()) {
- mActionButtonViewBinder.bind(mRemoteCopyChip,
- ActionButtonViewModel.Companion.withNextId(
- new ActionButtonAppearance(
- Icon.createWithResource(mContext,
- R.drawable.ic_baseline_devices_24).loadDrawable(
- mContext),
- null,
- mContext.getString(R.string.clipboard_send_nearby_description),
- true),
- new Function0<>() {
- @Override
- public Unit invoke() {
- if (mClipboardCallbacks != null) {
- mClipboardCallbacks.onRemoteCopyButtonTapped();
- }
- return null;
+ mActionButtonViewBinder.bind(mRemoteCopyChip,
+ ActionButtonViewModel.Companion.withNextId(
+ new ActionButtonAppearance(
+ Icon.createWithResource(mContext,
+ R.drawable.ic_baseline_devices_24).loadDrawable(
+ mContext),
+ null,
+ mContext.getString(R.string.clipboard_send_nearby_description),
+ true),
+ new Function0<>() {
+ @Override
+ public Unit invoke() {
+ if (mClipboardCallbacks != null) {
+ mClipboardCallbacks.onRemoteCopyButtonTapped();
}
- }));
- mActionButtonViewBinder.bind(mShareChip,
- ActionButtonViewModel.Companion.withNextId(
- new ActionButtonAppearance(
- Icon.createWithResource(mContext,
- R.drawable.ic_screenshot_share).loadDrawable(mContext),
- null,
- mContext.getString(com.android.internal.R.string.share),
- true),
- new Function0<>() {
- @Override
- public Unit invoke() {
- if (mClipboardCallbacks != null) {
- mClipboardCallbacks.onShareButtonTapped();
- }
- return null;
+ return null;
+ }
+ }));
+ mActionButtonViewBinder.bind(mShareChip,
+ ActionButtonViewModel.Companion.withNextId(
+ new ActionButtonAppearance(
+ Icon.createWithResource(mContext,
+ R.drawable.ic_screenshot_share).loadDrawable(mContext),
+ null,
+ mContext.getString(com.android.internal.R.string.share),
+ true),
+ new Function0<>() {
+ @Override
+ public Unit invoke() {
+ if (mClipboardCallbacks != null) {
+ mClipboardCallbacks.onShareButtonTapped();
}
- }));
- } else {
- mShareChip.setAlpha(1);
- mRemoteCopyChip.setAlpha(1);
-
- ((ImageView) mRemoteCopyChip.findViewById(R.id.overlay_action_chip_icon)).setImageIcon(
- Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24));
- ((ImageView) mShareChip.findViewById(R.id.overlay_action_chip_icon)).setImageIcon(
- Icon.createWithResource(mContext, R.drawable.ic_screenshot_share));
-
- mShareChip.setContentDescription(
- mContext.getString(com.android.internal.R.string.share));
- mRemoteCopyChip.setContentDescription(
- mContext.getString(R.string.clipboard_send_nearby_description));
- }
+ return null;
+ }
+ }));
}
@Override
public void setCallbacks(SwipeDismissCallbacks callbacks) {
super.setCallbacks(callbacks);
ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks;
- if (!screenshotShelfUi2()) {
- mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped());
- mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
- }
mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
mMinimizedPreview.setOnClickListener(v -> clipboardCallbacks.onMinimizedViewTapped());
@@ -495,12 +473,7 @@
void setActionChip(RemoteAction action, Runnable onFinish) {
mActionContainerBackground.setVisibility(View.VISIBLE);
- View chip;
- if (screenshotShelfUi2()) {
- chip = constructShelfActionChip(action, onFinish);
- } else {
- chip = constructActionChip(action, onFinish);
- }
+ View chip = constructShelfActionChip(action, onFinish);
mActionContainer.addView(chip);
mActionChips.add(chip);
}
@@ -534,17 +507,6 @@
return chip;
}
- private OverlayActionChip constructActionChip(RemoteAction action, Runnable onFinish) {
- OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
- R.layout.overlay_action_chip, mActionContainer, false);
- chip.setText(action.getTitle());
- chip.setContentDescription(action.getTitle());
- chip.setIcon(action.getIcon(), false);
- chip.setPendingIntent(action.getActionIntent(), onFinish);
- chip.setAlpha(1);
- return chip;
- }
-
private static void updateTextSize(CharSequence text, TextView textView) {
Paint paint = new Paint(textView.getPaint());
Resources res = textView.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
index 740a93e..ff9fba4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -18,8 +18,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
-import static com.android.systemui.Flags.screenshotShelfUi2;
-
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import android.content.Context;
@@ -59,13 +57,8 @@
*/
@Provides
static ClipboardOverlayView provideClipboardOverlayView(@OverlayWindowContext Context context) {
- if (screenshotShelfUi2()) {
- return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
- R.layout.clipboard_overlay2, null);
- } else {
- return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
- R.layout.clipboard_overlay, null);
- }
+ return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
+ R.layout.clipboard_overlay, null);
}
@Qualifier
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index f5255ac..86f5fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -20,6 +20,7 @@
import android.content.ComponentName
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
@@ -386,11 +387,11 @@
combine(
widgetRepository.communalWidgets
.map { filterWidgetsByExistingUsers(it) }
- .combine(communalSettingsInteractor.allowedByDevicePolicyForWorkProfile) {
+ .combine(communalSettingsInteractor.workProfileUserDisallowedByDevicePolicy) {
// exclude widgets under work profile if not allowed by device policy
widgets,
- allowedForWorkProfile ->
- filterWidgetsAllowedByDevicePolicy(widgets, allowedForWorkProfile)
+ disallowedByPolicyUser ->
+ filterWidgetsAllowedByDevicePolicy(widgets, disallowedByPolicyUser)
},
updateOnWorkProfileBroadcastReceived,
) { widgets, _ ->
@@ -418,13 +419,11 @@
/** Filter widgets based on whether their associated profile is allowed by device policy. */
private fun filterWidgetsAllowedByDevicePolicy(
list: List<CommunalWidgetContentModel>,
- allowedByDevicePolicyForWorkProfile: Boolean
+ disallowedByDevicePolicyUser: UserInfo?
): List<CommunalWidgetContentModel> =
- if (allowedByDevicePolicyForWorkProfile) {
+ if (disallowedByDevicePolicyUser == null) {
list
} else {
- // Get associated work profile for the currently selected user.
- val workProfile = userTracker.userProfiles.find { it.isManagedProfile }
list.filter { model ->
val uid =
when (model) {
@@ -432,7 +431,7 @@
model.providerInfo.profile.identifier
is CommunalWidgetContentModel.Pending -> model.user.identifier
}
- uid != workProfile?.id
+ uid != disallowedByDevicePolicyUser.id
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 47b75c4..3b01aec 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -92,15 +92,22 @@
awaitClose { userTracker.removeCallback(callback) }
}
- /** Whether or not keyguard widgets are allowed for work profile by device policy manager. */
- val allowedByDevicePolicyForWorkProfile: StateFlow<Boolean> =
+ /**
+ * A user that device policy says shouldn't allow communal widgets, or null if there are no
+ * restrictions.
+ */
+ val workProfileUserDisallowedByDevicePolicy: StateFlow<UserInfo?> =
workProfileUserInfoCallbackFlow
.flatMapLatest { workProfile ->
- workProfile?.let { repository.getAllowedByDevicePolicy(it) } ?: flowOf(false)
+ workProfile?.let {
+ repository.getAllowedByDevicePolicy(it).map { allowed ->
+ if (!allowed) it else null
+ }
+ } ?: flowOf(null)
}
.stateIn(
scope = bgScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = null
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 4f54fee..18b343e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -186,6 +186,10 @@
AppWidgetManager.EXTRA_CATEGORY_FILTER,
CommunalWidgetCategories.defaultCategories
)
+
+ communalSettingsInteractor.workProfileUserDisallowedByDevicePolicy.value?.let {
+ putExtra(EXTRA_USER_ID_FILTER, arrayListOf(it.id))
+ }
putExtra(EXTRA_UI_SURFACE_KEY, EXTRA_UI_SURFACE_VALUE)
putExtra(EXTRA_PICKER_TITLE, resources.getString(R.string.communal_widget_picker_title))
putExtra(
@@ -223,6 +227,7 @@
private const val EXTRA_PICKER_DESCRIPTION = "picker_description"
private const val EXTRA_UI_SURFACE_KEY = "ui_surface"
private const val EXTRA_UI_SURFACE_VALUE = "widgets_hub"
+ private const val EXTRA_USER_ID_FILTER = "filtered_user_ids"
const val EXTRA_ADDED_APP_WIDGETS_KEY = "added_app_widgets"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 572283a..1771f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -61,6 +61,7 @@
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.education.dagger.ContextualEducationModule;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
@@ -110,6 +111,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.chips.StatusBarChipsModule;
import com.android.systemui.statusbar.connectivity.ConnectivityModule;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.disableflags.dagger.DisableFlagsModule;
@@ -244,6 +246,7 @@
SmartspaceModule.class,
StatusBarEventsModule.class,
StatusBarModule.class,
+ StatusBarChipsModule.class,
StatusBarPipelineModule.class,
StatusBarPolicyModule.class,
StatusBarViewBinderModule.class,
@@ -259,7 +262,8 @@
UserModule.class,
UtilModule.class,
NoteTaskModule.class,
- WalletModule.class
+ WalletModule.class,
+ ContextualEducationModule.class
},
subcomponents = {
ComplicationComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
index 297ad84..befd822 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
@@ -161,5 +161,6 @@
TASK_FRAGMENT_TRANSIT_CLOSE,
false
)
+ organizer.unregisterOrganizer()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
new file mode 100644
index 0000000..e2bcb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 com.android.systemui.education.dagger
+
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+
+@Module
+interface ContextualEducationModule {
+ @Qualifier annotation class EduDataStoreScope
+
+ companion object {
+ @EduDataStoreScope
+ @Provides
+ fun provideEduDataStoreScope(
+ @Background bgDispatcher: CoroutineDispatcher
+ ): CoroutineScope {
+ return CoroutineScope(bgDispatcher + SupervisorJob())
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
new file mode 100644
index 0000000..af35e8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -0,0 +1,26 @@
+/*
+ * 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 com.android.systemui.education.data.model
+
+/**
+ * Model to store education data related to each gesture (e.g. Back, Home, All Apps, Overview). Each
+ * gesture stores its own model separately.
+ */
+data class GestureEduModel(
+ val signalCount: Int,
+ val educationShownCount: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
new file mode 100644
index 0000000..c9dd833
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * 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 com.android.systemui.education.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.education.GestureType
+import javax.inject.Inject
+
+/**
+ * Provide methods to read and update on field level and allow setting datastore when user is
+ * changed
+ */
+@SysUISingleton
+class ContextualEducationRepository
+@Inject
+constructor(private val userEduRepository: UserContextualEducationRepository) {
+ /** To change data store when user is changed */
+ fun setUser(userId: Int) = userEduRepository.setUser(userId)
+
+ fun readGestureEduModelFlow(gestureType: GestureType) =
+ userEduRepository.readGestureEduModelFlow(gestureType)
+
+ suspend fun incrementSignalCount(gestureType: GestureType) {
+ userEduRepository.updateGestureEduModel(gestureType) {
+ it.copy(signalCount = it.signalCount + 1)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
new file mode 100644
index 0000000..229511a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -0,0 +1,114 @@
+/*
+ * 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 com.android.systemui.education.data.repository
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.PreferenceDataStoreFactory
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.preferencesDataStoreFile
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.shared.education.GestureType
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * A contextual education repository to:
+ * 1) store education data per user
+ * 2) provide methods to read and update data on model-level
+ * 3) provide method to enable changing datastore when user is changed
+ */
+@SysUISingleton
+class UserContextualEducationRepository
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope>
+) {
+ companion object {
+ const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
+ const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
+
+ const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
+ }
+
+ private var dataStoreScope: CoroutineScope? = null
+
+ private val datastore = MutableStateFlow<DataStore<Preferences>?>(null)
+
+ @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
+ private val prefData: Flow<Preferences> = datastore.filterNotNull().flatMapLatest { it.data }
+
+ internal fun setUser(userId: Int) {
+ dataStoreScope?.cancel()
+ val newDsScope = dataStoreScopeProvider.get()
+ datastore.value =
+ PreferenceDataStoreFactory.create(
+ produceFile = {
+ applicationContext.preferencesDataStoreFile(
+ String.format(DATASTORE_DIR, userId)
+ )
+ },
+ scope = newDsScope,
+ )
+ dataStoreScope = newDsScope
+ }
+
+ internal fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> =
+ prefData.map { preferences -> getGestureEduModel(gestureType, preferences) }
+
+ private fun getGestureEduModel(
+ gestureType: GestureType,
+ preferences: Preferences
+ ): GestureEduModel {
+ return GestureEduModel(
+ signalCount = preferences[getSignalCountKey(gestureType)] ?: 0,
+ educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0,
+ )
+ }
+
+ internal suspend fun updateGestureEduModel(
+ gestureType: GestureType,
+ transform: (GestureEduModel) -> GestureEduModel
+ ) {
+ datastore.filterNotNull().first().edit { preferences ->
+ val currentModel = getGestureEduModel(gestureType, preferences)
+ val updatedModel = transform(currentModel)
+ preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount
+ preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount
+ }
+ }
+
+ private fun getSignalCountKey(gestureType: GestureType): Preferences.Key<Int> =
+ intPreferencesKey(gestureType.name + SIGNAL_COUNT_SUFFIX)
+
+ private fun getEducationShownCountKey(gestureType: GestureType): Preferences.Key<Int> =
+ intPreferencesKey(gestureType.name + NUMBER_OF_EDU_SHOWN_SUFFIX)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
new file mode 100644
index 0000000..3b161b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 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 com.android.systemui.inputdevice.data.repository
+
+import android.annotation.SuppressLint
+import android.hardware.input.InputManager
+import android.os.Handler
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+
+@SysUISingleton
+class InputDeviceRepository
+@Inject
+constructor(
+ @Background private val backgroundHandler: Handler,
+ @Background private val backgroundScope: CoroutineScope,
+ private val inputManager: InputManager
+) {
+
+ sealed interface DeviceChange
+
+ data class DeviceAdded(val deviceId: Int) : DeviceChange
+
+ data object DeviceRemoved : DeviceChange
+
+ data object FreshStart : DeviceChange
+
+ /**
+ * Emits collection of all currently connected keyboards and what was the last [DeviceChange].
+ * It emits collection so that every new subscriber to this SharedFlow can get latest state of
+ * all keyboards. Otherwise we might get into situation where subscriber timing on
+ * initialization matter and later subscriber will only get latest device and will miss all
+ * previous devices.
+ */
+ // TODO(b/351984587): Replace with StateFlow
+ @SuppressLint("SharedFlowCreation")
+ val deviceChange: Flow<Pair<Collection<Int>, DeviceChange>> =
+ conflatedCallbackFlow {
+ var connectedDevices = inputManager.inputDeviceIds.toSet()
+ val listener =
+ object : InputManager.InputDeviceListener {
+ override fun onInputDeviceAdded(deviceId: Int) {
+ connectedDevices = connectedDevices + deviceId
+ sendWithLogging(connectedDevices to DeviceAdded(deviceId))
+ }
+
+ override fun onInputDeviceChanged(deviceId: Int) = Unit
+
+ override fun onInputDeviceRemoved(deviceId: Int) {
+ connectedDevices = connectedDevices - deviceId
+ sendWithLogging(connectedDevices to DeviceRemoved)
+ }
+ }
+ sendWithLogging(connectedDevices to FreshStart)
+ inputManager.registerInputDeviceListener(listener, backgroundHandler)
+ awaitClose { inputManager.unregisterInputDeviceListener(listener) }
+ }
+ .shareIn(
+ scope = backgroundScope,
+ started = SharingStarted.Lazily,
+ replay = 1,
+ )
+
+ private fun <T> SendChannel<T>.sendWithLogging(element: T) {
+ trySendWithFailureLogging(element, TAG)
+ }
+
+ companion object {
+ const val TAG = "InputDeviceRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
index 91d5280..817849c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
@@ -21,21 +21,23 @@
import android.hardware.input.InputManager.KeyboardBacklightListener
import android.hardware.input.KeyboardBacklightState
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceAdded
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceChange
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceRemoved
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.FreshStart
import com.android.systemui.keyboard.data.model.Keyboard
import com.android.systemui.keyboard.shared.model.BacklightModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
@@ -44,7 +46,6 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.shareIn
/**
* Provides information about physical keyboard states. [CommandLineKeyboardRepository] can be
@@ -71,50 +72,15 @@
class KeyboardRepositoryImpl
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val inputManager: InputManager,
+ inputDeviceRepository: InputDeviceRepository
) : KeyboardRepository {
- private sealed interface DeviceChange
- private data class DeviceAdded(val deviceId: Int) : DeviceChange
- private object DeviceRemoved : DeviceChange
- private object FreshStart : DeviceChange
-
- /**
- * Emits collection of all currently connected keyboards and what was the last [DeviceChange].
- * It emits collection so that every new subscriber to this SharedFlow can get latest state of
- * all keyboards. Otherwise we might get into situation where subscriber timing on
- * initialization matter and later subscriber will only get latest device and will miss all
- * previous devices.
- */
private val keyboardsChange: Flow<Pair<Collection<Int>, DeviceChange>> =
- conflatedCallbackFlow {
- var connectedDevices = inputManager.inputDeviceIds.toSet()
- val listener =
- object : InputManager.InputDeviceListener {
- override fun onInputDeviceAdded(deviceId: Int) {
- connectedDevices = connectedDevices + deviceId
- sendWithLogging(connectedDevices to DeviceAdded(deviceId))
- }
-
- override fun onInputDeviceChanged(deviceId: Int) = Unit
-
- override fun onInputDeviceRemoved(deviceId: Int) {
- connectedDevices = connectedDevices - deviceId
- sendWithLogging(connectedDevices to DeviceRemoved)
- }
- }
- sendWithLogging(connectedDevices to FreshStart)
- inputManager.registerInputDeviceListener(listener, /* handler= */ null)
- awaitClose { inputManager.unregisterInputDeviceListener(listener) }
- }
- .map { (ids, change) -> ids.filter { id -> isPhysicalFullKeyboard(id) } to change }
- .shareIn(
- scope = applicationScope,
- started = SharingStarted.Lazily,
- replay = 1,
- )
+ inputDeviceRepository.deviceChange.map { (ids, change) ->
+ ids.filter { id -> isPhysicalFullKeyboard(id) } to change
+ }
@FlowPreview
override val newlyConnectedKeyboard: Flow<Keyboard> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
index 6364a6f..1f0aef8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
@@ -20,10 +20,12 @@
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.keyboardShortcutHelperRewrite
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
+import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
+import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
@@ -56,6 +58,12 @@
@InputShortcuts
fun inputShortcutsSources(impl: InputShortcutsSource): KeyboardShortcutGroupsSource
+ @Binds
+ @AppCategoriesShortcuts
+ fun appCategoriesShortcutsSource(
+ impl: AppCategoriesShortcutsSource
+ ): KeyboardShortcutGroupsSource
+
companion object {
@Provides
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index 133dab6..7b0c25e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
+import android.graphics.drawable.Icon
import android.hardware.input.InputManager
import android.util.Log
import android.view.KeyCharacterMap
@@ -26,17 +27,20 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.APP_CATEGORIES
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
@@ -52,6 +56,7 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
@SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
@MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
+ @AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource,
@InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
private val inputManager: InputManager,
stateRepository: ShortcutHelperStateRepository
@@ -72,7 +77,8 @@
toShortcutCategory(
it.keyCharacterMap,
SYSTEM,
- systemShortcutsSource.shortcutGroups(it.id)
+ systemShortcutsSource.shortcutGroups(it.id),
+ keepIcons = true,
)
} else {
null
@@ -85,7 +91,22 @@
toShortcutCategory(
it.keyCharacterMap,
MULTI_TASKING,
- multitaskingShortcutsSource.shortcutGroups(it.id)
+ multitaskingShortcutsSource.shortcutGroups(it.id),
+ keepIcons = true,
+ )
+ } else {
+ null
+ }
+ }
+
+ val appCategoriesShortcutsCategory =
+ activeInputDevice.map {
+ if (it != null) {
+ toShortcutCategory(
+ it.keyCharacterMap,
+ APP_CATEGORIES,
+ appCategoriesShortcutsSource.shortcutGroups(it.id),
+ keepIcons = true,
)
} else {
null
@@ -98,7 +119,8 @@
toShortcutCategory(
it.keyCharacterMap,
IME,
- inputShortcutsSource.shortcutGroups(it.id)
+ inputShortcutsSource.shortcutGroups(it.id),
+ keepIcons = false,
)
} else {
null
@@ -109,13 +131,14 @@
keyCharacterMap: KeyCharacterMap,
type: ShortcutCategoryType,
shortcutGroups: List<KeyboardShortcutGroup>,
+ keepIcons: Boolean,
): ShortcutCategory? {
val subCategories =
shortcutGroups
.map { shortcutGroup ->
ShortcutSubCategory(
shortcutGroup.label.toString(),
- toShortcuts(keyCharacterMap, shortcutGroup.items)
+ toShortcuts(keyCharacterMap, shortcutGroup.items, keepIcons)
)
}
.filter { it.shortcuts.isNotEmpty() }
@@ -129,16 +152,37 @@
private fun toShortcuts(
keyCharacterMap: KeyCharacterMap,
- infoList: List<KeyboardShortcutInfo>
- ) = infoList.mapNotNull { toShortcut(keyCharacterMap, it) }
+ infoList: List<KeyboardShortcutInfo>,
+ keepIcons: Boolean,
+ ) = infoList.mapNotNull { toShortcut(keyCharacterMap, it, keepIcons) }
private fun toShortcut(
keyCharacterMap: KeyCharacterMap,
- shortcutInfo: KeyboardShortcutInfo
+ shortcutInfo: KeyboardShortcutInfo,
+ keepIcon: Boolean,
): Shortcut? {
- val shortcutCommand = toShortcutCommand(keyCharacterMap, shortcutInfo)
- return if (shortcutCommand == null) null
- else Shortcut(label = shortcutInfo.label!!.toString(), commands = listOf(shortcutCommand))
+ val shortcutCommand = toShortcutCommand(keyCharacterMap, shortcutInfo) ?: return null
+ return Shortcut(
+ label = shortcutInfo.label!!.toString(),
+ icon = toShortcutIcon(keepIcon, shortcutInfo),
+ commands = listOf(shortcutCommand)
+ )
+ }
+
+ private fun toShortcutIcon(
+ keepIcon: Boolean,
+ shortcutInfo: KeyboardShortcutInfo
+ ): ShortcutIcon? {
+ if (!keepIcon) {
+ return null
+ }
+ val icon = shortcutInfo.icon ?: return null
+ // For now only keep icons of type resource, which is what the "default apps" shortcuts
+ // provide.
+ if (icon.type != Icon.TYPE_RESOURCE || icon.resPackage.isNullOrEmpty() || icon.resId <= 0) {
+ return null
+ }
+ return ShortcutIcon(packageName = icon.resPackage, resourceId = icon.resId)
}
private fun toShortcutCommand(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSource.kt
new file mode 100644
index 0000000..d7cb7db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSource.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 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 com.android.systemui.keyboard.shortcut.data.source
+
+import android.content.Intent
+import android.content.res.Resources
+import android.view.KeyEvent
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.util.icons.AppCategoryIconProvider
+import javax.inject.Inject
+
+class AppCategoriesShortcutsSource
+@Inject
+constructor(
+ private val appCategoryIconProvider: AppCategoryIconProvider,
+ @Main private val resources: Resources,
+) : KeyboardShortcutGroupsSource {
+
+ override suspend fun shortcutGroups(deviceId: Int) =
+ listOf(
+ KeyboardShortcutGroup(
+ /* label = */ resources.getString(R.string.keyboard_shortcut_group_applications),
+ /* items = */ shortcuts()
+ )
+ )
+
+ private suspend fun shortcuts(): List<KeyboardShortcutInfo> =
+ listOfNotNull(
+ assistantAppShortcutInfo(),
+ appCategoryShortcutInfo(
+ Intent.CATEGORY_APP_BROWSER,
+ R.string.keyboard_shortcut_group_applications_browser,
+ KeyEvent.KEYCODE_B
+ ),
+ appCategoryShortcutInfo(
+ Intent.CATEGORY_APP_CONTACTS,
+ R.string.keyboard_shortcut_group_applications_contacts,
+ KeyEvent.KEYCODE_C
+ ),
+ appCategoryShortcutInfo(
+ Intent.CATEGORY_APP_EMAIL,
+ R.string.keyboard_shortcut_group_applications_email,
+ KeyEvent.KEYCODE_E
+ ),
+ appCategoryShortcutInfo(
+ Intent.CATEGORY_APP_CALENDAR,
+ R.string.keyboard_shortcut_group_applications_calendar,
+ KeyEvent.KEYCODE_K
+ ),
+ appCategoryShortcutInfo(
+ Intent.CATEGORY_APP_MAPS,
+ R.string.keyboard_shortcut_group_applications_maps,
+ KeyEvent.KEYCODE_M
+ ),
+ appCategoryShortcutInfo(
+ Intent.CATEGORY_APP_MUSIC,
+ R.string.keyboard_shortcut_group_applications_music,
+ KeyEvent.KEYCODE_P
+ ),
+ appCategoryShortcutInfo(
+ Intent.CATEGORY_APP_MESSAGING,
+ R.string.keyboard_shortcut_group_applications_sms,
+ KeyEvent.KEYCODE_S
+ ),
+ appCategoryShortcutInfo(
+ Intent.CATEGORY_APP_CALCULATOR,
+ R.string.keyboard_shortcut_group_applications_calculator,
+ KeyEvent.KEYCODE_U
+ ),
+ )
+ .sortedBy { it.label!!.toString().lowercase() }
+
+ private suspend fun assistantAppShortcutInfo(): KeyboardShortcutInfo? {
+ val assistantIcon = appCategoryIconProvider.assistantAppIcon() ?: return null
+ return KeyboardShortcutInfo(
+ /* label = */ resources.getString(R.string.keyboard_shortcut_group_applications_assist),
+ /* icon = */ assistantIcon,
+ /* keycode = */ KeyEvent.KEYCODE_A,
+ /* modifiers = */ KeyEvent.META_META_ON,
+ )
+ }
+
+ private suspend fun appCategoryShortcutInfo(category: String, labelResId: Int, keycode: Int) =
+ KeyboardShortcutInfo(
+ /* label = */ resources.getString(labelResId),
+ /* icon = */ appCategoryIconProvider.categoryAppIcon(category),
+ /* keycode = */ keycode,
+ /* modifiers = */ KeyEvent.META_META_ON,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index ead10e5..d41d21a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -24,7 +24,6 @@
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
@SysUISingleton
class ShortcutHelperCategoriesInteractor
@@ -33,13 +32,13 @@
categoriesRepository: ShortcutHelperCategoriesRepository,
) {
- private val systemsShortcutCategory = categoriesRepository.systemShortcutsCategory
- private val multitaskingShortcutsCategory = categoriesRepository.multitaskingShortcutsCategory
- private val imeShortcutsCategory = categoriesRepository.imeShortcutsCategory
-
val shortcutCategories: Flow<List<ShortcutCategory>> =
- combine(systemsShortcutCategory, multitaskingShortcutsCategory, imeShortcutsCategory) {
- shortcutCategories ->
+ combine(
+ categoriesRepository.systemShortcutsCategory,
+ categoriesRepository.multitaskingShortcutsCategory,
+ categoriesRepository.imeShortcutsCategory,
+ categoriesRepository.appCategoriesShortcutsCategory,
+ ) { shortcutCategories ->
shortcutCategories.filterNotNull().map { groupSubCategoriesInCategory(it) }
}
@@ -62,6 +61,10 @@
.groupBy { it.label }
.entries
.map { (commonLabel, groupedShortcuts) ->
- Shortcut(label = commonLabel, commands = groupedShortcuts.flatMap { it.commands })
+ Shortcut(
+ label = commonLabel,
+ icon = groupedShortcuts.firstOrNull()?.icon,
+ commands = groupedShortcuts.flatMap { it.commands }
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AppCategoriesShortcuts.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AppCategoriesShortcuts.kt
new file mode 100644
index 0000000..b105ff6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AppCategoriesShortcuts.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 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 com.android.systemui.keyboard.shortcut.qualifiers
+
+import javax.inject.Qualifier
+
+@Qualifier annotation class AppCategoriesShortcuts
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
index adc6d95..5f8570c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
@@ -16,7 +16,11 @@
package com.android.systemui.keyboard.shortcut.shared.model
-data class Shortcut(val label: String, val commands: List<ShortcutCommand>)
+data class Shortcut(
+ val label: String,
+ val commands: List<ShortcutCommand>,
+ val icon: ShortcutIcon? = null,
+)
class ShortcutBuilder(private val label: String) {
val commands = mutableListOf<ShortcutCommand>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index 5d05359..63e167a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -19,7 +19,8 @@
enum class ShortcutCategoryType {
SYSTEM,
MULTI_TASKING,
- IME
+ IME,
+ APP_CATEGORIES,
}
data class ShortcutCategory(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutIcon.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutIcon.kt
new file mode 100644
index 0000000..8b720a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutIcon.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 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 com.android.systemui.keyboard.shortcut.shared.model
+
+data class ShortcutIcon(val packageName: String, val resourceId: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index b703892..3b037bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -16,8 +16,10 @@
package com.android.systemui.keyboard.shortcut.ui.composable
+import android.graphics.drawable.Icon
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -43,6 +45,7 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
+import androidx.compose.material.icons.filled.Apps
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Keyboard
import androidx.compose.material.icons.filled.Search
@@ -75,6 +78,7 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -86,11 +90,13 @@
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
@@ -221,6 +227,7 @@
ShortcutCategoryType.SYSTEM -> Icons.Default.Tv
ShortcutCategoryType.MULTI_TASKING -> Icons.Default.VerticalSplit
ShortcutCategoryType.IME -> Icons.Default.Keyboard
+ ShortcutCategoryType.APP_CATEGORIES -> Icons.Default.Apps
}
private val ShortcutCategory.labelResId: Int
@@ -229,6 +236,7 @@
ShortcutCategoryType.SYSTEM -> R.string.shortcut_helper_category_system
ShortcutCategoryType.MULTI_TASKING -> R.string.shortcut_helper_category_multitasking
ShortcutCategoryType.IME -> R.string.shortcut_helper_category_input
+ ShortcutCategoryType.APP_CATEGORIES -> R.string.shortcut_helper_category_app_shortcuts
}
@Composable
@@ -359,10 +367,21 @@
@Composable
private fun ShortcutViewDualPane(shortcut: Shortcut) {
Row(Modifier.padding(vertical = 16.dp)) {
- ShortcutDescriptionText(
+ Row(
modifier = Modifier.width(160.dp).align(Alignment.CenterVertically),
- shortcut = shortcut,
- )
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ if (shortcut.icon != null) {
+ ShortcutIcon(
+ shortcut.icon,
+ modifier = Modifier.size(36.dp),
+ )
+ }
+ ShortcutDescriptionText(
+ shortcut = shortcut,
+ )
+ }
Spacer(modifier = Modifier.width(16.dp))
ShortcutKeyCombinations(
modifier = Modifier.weight(1f),
@@ -371,6 +390,24 @@
}
}
+@Composable
+fun ShortcutIcon(
+ icon: ShortcutIcon,
+ modifier: Modifier = Modifier,
+ contentDescription: String? = null,
+) {
+ val context = LocalContext.current
+ val drawable =
+ remember(icon.packageName, icon.resourceId) {
+ Icon.createWithResource(icon.packageName, icon.resourceId).loadDrawable(context)
+ } ?: return
+ Image(
+ painter = rememberDrawablePainter(drawable),
+ contentDescription = contentDescription,
+ modifier = modifier,
+ )
+}
+
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun ShortcutKeyCombinations(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index c4b70d8..9f33113 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -81,6 +81,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -317,7 +318,7 @@
private final WindowManagerOcclusionManager mWmOcclusionManager;
private final KeyguardEnabledInteractor mKeyguardEnabledInteractor;
-
+ private final KeyguardWakeDirectlyToGoneInteractor mKeyguardWakeDirectlyToGoneInteractor;
private final Lazy<FoldGracePeriodProvider> mFoldGracePeriodProvider = new Lazy<>() {
@Override
public FoldGracePeriodProvider get() {
@@ -344,7 +345,8 @@
@Main Executor mainExecutor,
KeyguardInteractor keyguardInteractor,
KeyguardEnabledInteractor keyguardEnabledInteractor,
- Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy) {
+ Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy,
+ KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -372,6 +374,7 @@
mWmOcclusionManager = windowManagerOcclusionManager;
mKeyguardEnabledInteractor = keyguardEnabledInteractor;
+ mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor;
}
@Override
@@ -486,6 +489,7 @@
public void onDreamingStarted() {
trace("onDreamingStarted");
checkPermission();
+ mKeyguardWakeDirectlyToGoneInteractor.onDreamingStarted();
mKeyguardInteractor.setDreaming(true);
mKeyguardViewMediator.onDreamingStarted();
}
@@ -494,6 +498,7 @@
public void onDreamingStopped() {
trace("onDreamingStopped");
checkPermission();
+ mKeyguardWakeDirectlyToGoneInteractor.onDreamingStopped();
mKeyguardInteractor.setDreaming(false);
mKeyguardViewMediator.onDreamingStopped();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index b32d0950..d1a8463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -103,14 +103,14 @@
* swiped away via a touch gesture, or when it's flinging expanded/collapsed after a swipe.
*/
const val LEGACY_UNLOCK_ANIMATION_DURATION_MS = 200L
-const val UNLOCK_ANIMATION_DURATION_MS = 167L
+const val UNLOCK_ANIMATION_DURATION_MS = 300L
/**
* If there are two different wallpapers on home and lock screen, duration and delay of the lock
* wallpaper fade out.
*/
-const val LOCK_WALLPAPER_FADE_OUT_DURATION = 140L
-const val LOCK_WALLPAPER_FADE_OUT_START_DELAY = 0L
+const val LOCK_WALLPAPER_FADE_OUT_DURATION = 150L
+const val LOCK_WALLPAPER_FADE_OUT_START_DELAY = 150L
/**
* How long the in-window launcher icon animation takes. This is used if the launcher is underneath
@@ -167,7 +167,7 @@
private val statusBarStateController: SysuiStatusBarStateController,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val powerManager: PowerManager,
- private val wallpaperManager: WallpaperManager
+ private val wallpaperManager: WallpaperManager,
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -380,7 +380,6 @@
else LAUNCHER_ICONS_ANIMATION_DURATION_MS
interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR
else Interpolators.ALPHA_OUT
- if (fasterUnlockTransition()) startDelay = CANNED_UNLOCK_START_DELAY
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
valueAnimator.animatedValue as Float, openingWallpaperTargets)
@@ -647,14 +646,12 @@
val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
- val duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LAUNCHER_ICONS_ANIMATION_DURATION_MS
listeners.forEach {
it.onUnlockAnimationStarted(
playingCannedUnlockAnimation /* playingCannedAnimation */,
isWakeAndUnlockNotFromDream /* isWakeAndUnlockNotFromDream */,
cannedUnlockStartDelayMs() /* unlockStartDelay */,
- duration /* unlockAnimationDuration */) }
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
// Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
// Check it here in case there is no more change to the dismiss amount after the last change
@@ -746,6 +743,10 @@
// As soon as the shade starts animating out of the way, start the canned unlock animation,
// which will finish keyguard exit when it completes. The in-window animations in the
// Launcher window will end on their own.
+ if (fasterUnlockTransition() && openingWallpaperTargets?.isNotEmpty() == true) {
+ fadeOutWallpaper()
+ }
+
handler.postDelayed({
if (keyguardViewMediator.get().isShowingAndNotOccluded &&
!keyguardStateController.isKeyguardGoingAway) {
@@ -754,9 +755,8 @@
return@postDelayed
}
- if ((openingWallpaperTargets?.isNotEmpty() == true)) {
+ if (openingWallpaperTargets?.isNotEmpty() == true) {
fadeInWallpaper()
- if (fasterUnlockTransition()) fadeOutWallpaper()
hideKeyguardViewAfterRemoteAnimation()
} else {
keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
@@ -1038,6 +1038,7 @@
surfaceBehindAlphaAnimator.cancel()
surfaceBehindEntryAnimator.cancel()
wallpaperCannedUnlockAnimator.cancel()
+ if (fasterUnlockTransition()) wallpaperFadeOutUnlockAnimator.cancel()
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTargets = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 608e25a..33f9209 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -42,6 +42,7 @@
import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -73,6 +74,7 @@
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -108,6 +110,7 @@
private val clockInteractor: KeyguardClockInteractor,
private val keyguardViewMediator: KeyguardViewMediator,
private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>,
+ @Main private val mainDispatcher: CoroutineDispatcher,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -215,6 +218,7 @@
vibratorHelper,
falsingManager,
keyguardViewMediator,
+ mainDispatcher,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 1ea5d1c..fe81b20c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2722,13 +2722,13 @@
if (mGoingToSleep) {
mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
- return;
- }
- setPendingLock(false); // user may have authenticated during the screen off animation
+ } else {
+ setPendingLock(false); // user may have authenticated during the screen off animation
- handleHide();
- mKeyguardInteractor.keyguardDoneAnimationsFinished();
- mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
+ handleHide();
+ mKeyguardInteractor.keyguardDoneAnimationsFinished();
+ mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
+ }
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index f837d8e..ae751db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -127,6 +127,30 @@
*/
val isKeyguardEnabled: StateFlow<Boolean>
+ /**
+ * Whether we can transition directly back to GONE from AOD/DOZING without any authentication
+ * events (such as a fingerprint wake and unlock), even though authentication would normally be
+ * required. This means that if you tap the screen or press the power button, you'll return
+ * directly to the unlocked app content without seeing the lockscreen, even if a secure
+ * authentication method (PIN/password/biometrics) is set.
+ *
+ * This is true in these cases:
+ * - The screen timed out, but the "lock after screen timeout" duration (default 5 seconds) has
+ * not yet elapsed.
+ * - The power button was pressed, but "power button instantly locks" is not enabled, and the
+ * "lock after screen timeout" duration has not elapsed.
+ *
+ * Note that this value specifically tells us if we can *ignore* authentication that would
+ * otherwise be required to transition from AOD/DOZING -> GONE. AOD/DOZING -> GONE is also
+ * possible if keyguard is disabled, either from an app request or because security is set to
+ * "none", but in that case, auth is not required so this boolean is not relevant.
+ *
+ * See [KeyguardWakeToGoneInteractor].
+ */
+ val canIgnoreAuthAndReturnToGone: StateFlow<Boolean>
+
+ fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean)
+
/** Is the always-on display available to be used? */
val isAodAvailable: StateFlow<Boolean>
@@ -386,6 +410,13 @@
MutableStateFlow(!lockPatternUtils.isLockScreenDisabled(userTracker.userId))
override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
+ private val _canIgnoreAuthAndReturnToGone = MutableStateFlow(false)
+ override val canIgnoreAuthAndReturnToGone = _canIgnoreAuthAndReturnToGone.asStateFlow()
+
+ override fun setCanIgnoreAuthAndReturnToGone(canWakeToGone: Boolean) {
+ _canIgnoreAuthAndReturnToGone.value = canWakeToGone
+ }
+
private val _isDozing = MutableStateFlow(statusBarStateController.isDozing)
override val isDozing: StateFlow<Boolean> = _isDozing.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 868c462..1167cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -53,6 +53,7 @@
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
+ private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
@@ -98,6 +99,7 @@
keyguardInteractor.primaryBouncerShowing,
keyguardInteractor.isKeyguardOccluded,
canDismissLockscreen,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
)
.collect {
(
@@ -107,6 +109,7 @@
primaryBouncerShowing,
isKeyguardOccludedLegacy,
canDismissLockscreen,
+ canWakeDirectlyToGone,
) ->
if (!maybeHandleInsecurePowerGesture()) {
val shouldTransitionToLockscreen =
@@ -131,8 +134,7 @@
val shouldTransitionToGone =
(!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) ||
- (KeyguardWmStateRefactor.isEnabled &&
- !deviceEntryRepository.isLockscreenEnabled())
+ (KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone)
if (shouldTransitionToGone) {
startTransitionTo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 76e88a2..aee65a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -55,6 +55,7 @@
private val communalInteractor: CommunalInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
+ private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
@@ -181,7 +182,7 @@
.sample(
communalInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
- canTransitionToGoneOnWake,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
keyguardInteractor.primaryBouncerShowing,
)
.collect {
@@ -189,27 +190,14 @@
_,
isIdleOnCommunal,
biometricUnlockState,
- canDismissLockscreen,
+ canWakeDirectlyToGone,
primaryBouncerShowing) ->
if (
!maybeStartTransitionToOccludedOrInsecureCamera() &&
// Handled by dismissFromDozing().
!isWakeAndUnlock(biometricUnlockState.mode)
) {
- if (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) {
- if (SceneContainerFlag.isEnabled) {
- // TODO(b/336576536): Check if adaptation for scene framework is
- // needed
- } else {
- startTransitionTo(
- KeyguardState.GONE,
- ownerReason = "waking from dozing"
- )
- }
- } else if (
- KeyguardWmStateRefactor.isEnabled &&
- !deviceEntryRepository.isLockscreenEnabled()
- ) {
+ if (canWakeDirectlyToGone) {
if (SceneContainerFlag.isEnabled) {
// TODO(b/336576536): Check if adaptation for scene framework is
// needed
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 0e76487..cfb161c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -23,6 +23,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
@@ -37,11 +38,14 @@
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class FromDreamingTransitionInteractor
@Inject
@@ -56,6 +60,7 @@
private val glanceableHubTransitions: GlanceableHubTransitions,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING,
@@ -72,7 +77,7 @@
listenForDreamingToOccluded()
listenForDreamingToGoneWhenDismissable()
listenForDreamingToGoneFromBiometricUnlock()
- listenForDreamingToLockscreen()
+ listenForDreamingToLockscreenOrGone()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
listenForDreamingToGlanceableHub()
@@ -132,17 +137,7 @@
@OptIn(FlowPreview::class)
private fun listenForDreamingToOccluded() {
- if (KeyguardWmStateRefactor.isEnabled) {
- scope.launch {
- combine(
- keyguardInteractor.isDreaming,
- keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
- ::Pair
- )
- .filterRelevantKeyguardStateAnd { (isDreaming, _) -> !isDreaming }
- .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
- }
- } else {
+ if (!KeyguardWmStateRefactor.isEnabled) {
scope.launch {
combine(
keyguardInteractor.isKeyguardOccluded,
@@ -168,21 +163,41 @@
}
}
- private fun listenForDreamingToLockscreen() {
+ private fun listenForDreamingToLockscreenOrGone() {
if (!KeyguardWmStateRefactor.isEnabled) {
return
}
scope.launch {
- keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
- .filterRelevantKeyguardStateAnd { onTop -> !onTop }
- .collect { startTransitionTo(KeyguardState.LOCKSCREEN) }
+ keyguardInteractor.isDreaming
+ .filter { !it }
+ .sample(deviceEntryInteractor.isUnlocked, ::Pair)
+ .collect { (_, dismissable) ->
+ // TODO(b/349837588): Add check for -> OCCLUDED.
+ if (dismissable) {
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "No longer dreaming; dismissable"
+ )
+ } else {
+ startTransitionTo(
+ KeyguardState.LOCKSCREEN,
+ ownerReason = "No longer dreaming"
+ )
+ }
+ }
}
}
private fun listenForDreamingToGoneWhenDismissable() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
- if (SceneContainerFlag.isEnabled) return
+ if (SceneContainerFlag.isEnabled) {
+ return // TODO(b/336576536): Check if adaptation for scene framework is needed
+ }
+
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
keyguardInteractor.isAbleToDream
.sampleCombine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 859326a..ec03a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -52,7 +52,6 @@
import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
-import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -251,13 +250,17 @@
/** Keyguard can be clipped at the top as the shade is dragged */
val topClippingBounds: Flow<Int?> by lazy {
- repository.topClippingBounds
- .sampleFilter(
+ combineTransform(
keyguardTransitionInteractor
.transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
- .onStart { emit(0f) }
- ) { goneValue ->
- goneValue != 1f
+ .map { it == 1f }
+ .onStart { emit(false) }
+ .distinctUntilChanged(),
+ repository.topClippingBounds
+ ) { isGone, topClippingBounds ->
+ if (!isGone) {
+ emit(topClippingBounds)
+ }
}
.distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
new file mode 100644
index 0000000..6a1b7cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 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 com.android.systemui.keyguard.domain.interactor
+
+import android.annotation.SuppressLint
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.provider.Settings
+import android.provider.Settings.Secure
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAsleepInState
+import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAwakeInState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SystemSettings
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Logic related to the ability to wake directly to GONE from asleep (AOD/DOZING), without going
+ * through LOCKSCREEN or a BOUNCER state.
+ *
+ * This is possible in the following scenarios:
+ * - The lockscreen is disabled, either from an app request (SUW does this), or by the security
+ * "None" setting.
+ * - A biometric authentication event occurred while we were asleep (fingerprint auth, etc). This
+ * specifically is referred to throughout the codebase as "wake and unlock".
+ * - The screen timed out, but the "lock after screen timeout" duration has not elapsed.
+ * - The power button was pressed, but "power button instantly locks" is disabled and the "lock
+ * after screen timeout" duration has not elapsed.
+ *
+ * In these cases, no (further) authentication is required, and we can transition directly from
+ * AOD/DOZING -> GONE.
+ */
+@SysUISingleton
+class KeyguardWakeDirectlyToGoneInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val context: Context,
+ private val repository: KeyguardRepository,
+ private val systemClock: SystemClock,
+ private val alarmManager: AlarmManager,
+ private val transitionInteractor: KeyguardTransitionInteractor,
+ private val powerInteractor: PowerInteractor,
+ private val secureSettings: SecureSettings,
+ private val lockPatternUtils: LockPatternUtils,
+ private val systemSettings: SystemSettings,
+ private val selectedUserInteractor: SelectedUserInteractor,
+) {
+
+ /**
+ * Whether the lockscreen was disabled as of the last wake/sleep event, according to
+ * LockPatternUtils.
+ *
+ * This will always be true if [repository.isKeyguardServiceEnabled]=false, but it can also be
+ * true when the keyguard service is enabled if the lockscreen has been disabled via adb using
+ * the `adb shell locksettings set-disabled true` command, which is often done in tests.
+ *
+ * Unlike keyguardServiceEnabled, changes to this value should *not* immediately show or hide
+ * the keyguard. If the lockscreen is disabled in this way, it will just not show on the next
+ * sleep/wake.
+ */
+ private val isLockscreenDisabled: Flow<Boolean> =
+ powerInteractor.isAwake.map { isLockscreenDisabled() }
+
+ /**
+ * Whether we can wake from AOD/DOZING directly to GONE, bypassing LOCKSCREEN/BOUNCER states.
+ *
+ * This is possible in the following cases:
+ * - Keyguard is disabled, either from an app request or from security being set to "None".
+ * - We're wake and unlocking (fingerprint auth occurred while asleep).
+ * - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
+ */
+ val canWakeDirectlyToGone =
+ combine(
+ repository.isKeyguardEnabled,
+ isLockscreenDisabled,
+ repository.biometricUnlockState,
+ repository.canIgnoreAuthAndReturnToGone,
+ ) {
+ keyguardEnabled,
+ isLockscreenDisabled,
+ biometricUnlockState,
+ canIgnoreAuthAndReturnToGone ->
+ (!keyguardEnabled || isLockscreenDisabled) ||
+ BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) ||
+ canIgnoreAuthAndReturnToGone
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Counter that is incremented every time we wake up or stop dreaming. Upon sleeping/dreaming,
+ * we put the current value of this counter into the intent extras of the timeout alarm intent.
+ * If this value has changed by the time we receive the intent, it is discarded since it's out
+ * of date.
+ */
+ var timeoutCounter = 0
+
+ var isAwake = false
+
+ private val broadcastReceiver: BroadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (DELAYED_KEYGUARD_ACTION == intent.action) {
+ val sequence = intent.getIntExtra(SEQ_EXTRA_KEY, 0)
+ synchronized(this) {
+ if (timeoutCounter == sequence) {
+ // If the sequence # matches, we have not woken up or stopped dreaming
+ // since
+ // the alarm was set. That means this is still relevant - the lock
+ // timeout
+ // has elapsed, so let the repository know that we can no longer return
+ // to
+ // GONE without authenticating.
+ repository.setCanIgnoreAuthAndReturnToGone(false)
+ }
+ }
+ }
+ }
+ }
+
+ init {
+ setOrCancelAlarmFromWakefulness()
+ listenForWakeToClearCanIgnoreAuth()
+ registerBroadcastReceiver()
+ }
+
+ fun onDreamingStarted() {
+ // If we start dreaming while awake, lock after the normal timeout.
+ if (isAwake) {
+ setResetCanIgnoreAuthAlarm()
+ }
+ }
+
+ fun onDreamingStopped() {
+ // Cancel the timeout if we stop dreaming while awake.
+ if (isAwake) {
+ cancelCanIgnoreAuthAlarm()
+ }
+ }
+
+ private fun setOrCancelAlarmFromWakefulness() {
+ scope.launch {
+ powerInteractor.detailedWakefulness
+ .distinctUntilChangedBy { it.isAwake() }
+ .sample(transitionInteractor.currentKeyguardState, ::Pair)
+ .collect { (wakefulness, currentState) ->
+ // Save isAwake for use in onDreamingStarted/onDreamingStopped.
+ this@KeyguardWakeDirectlyToGoneInteractor.isAwake = wakefulness.isAwake()
+
+ // If we're sleeping from GONE, check the timeout and lock instantly settings.
+ // These are not relevant if we're coming from non-GONE states.
+ if (!isAwake && currentState == KeyguardState.GONE) {
+ val lockTimeoutDuration = getCanIgnoreAuthAndReturnToGoneDuration()
+
+ // If the screen timed out and went to sleep, and the lock timeout is > 0ms,
+ // then we can return to GONE until that duration elapses. If the power
+ // button was pressed but "instantly locks" is disabled, then we can also
+ // return to GONE until the timeout duration elapses.
+ if (
+ (wakefulness.lastSleepReason == WakeSleepReason.TIMEOUT &&
+ lockTimeoutDuration > 0) ||
+ (wakefulness.lastSleepReason == WakeSleepReason.POWER_BUTTON &&
+ !willLockImmediately())
+ ) {
+
+ // Let the repository know that we can return to GONE until we notify
+ // it otherwise.
+ repository.setCanIgnoreAuthAndReturnToGone(true)
+ setResetCanIgnoreAuthAlarm()
+ }
+ } else if (isAwake) {
+ // If we're waking up, ignore the alarm if it goes off since it's no longer
+ // relevant. Once a wake KeyguardTransition is started, we'll also clear the
+ // canIgnoreAuthAndReturnToGone value in listenForWakeToClearCanIgnoreAuth.
+ cancelCanIgnoreAuthAlarm()
+ }
+ }
+ }
+ }
+
+ /** Clears the canIgnoreAuthAndReturnToGone value upon waking. */
+ private fun listenForWakeToClearCanIgnoreAuth() {
+ scope.launch {
+ transitionInteractor
+ .isInTransitionWhere(
+ fromStatePredicate = { deviceIsAsleepInState(it) },
+ toStatePredicate = { deviceIsAwakeInState(it) },
+ )
+ .collect {
+ // This value is reset when the timeout alarm fires, but if the device is woken
+ // back up before then, it needs to be reset here. The alarm is cancelled
+ // immediately upon waking up, but since this value is used by keyguard
+ // transition internals to decide whether we can transition to GONE, wait until
+ // that decision is made before resetting it.
+ repository.setCanIgnoreAuthAndReturnToGone(false)
+ }
+ }
+ }
+
+ /**
+ * Registers the broadcast receiver to receive the alarm intent.
+ *
+ * TODO(b/351817381): Investigate using BroadcastDispatcher vs. ignoring this lint warning.
+ */
+ @SuppressLint("WrongConstant", "RegisterReceiverViaContext")
+ private fun registerBroadcastReceiver() {
+ val delayedActionFilter = IntentFilter()
+ delayedActionFilter.addAction(KeyguardViewMediator.DELAYED_KEYGUARD_ACTION)
+ // TODO(b/346803756): Listen for DELAYED_LOCK_PROFILE_ACTION.
+ delayedActionFilter.priority = IntentFilter.SYSTEM_HIGH_PRIORITY
+ context.registerReceiver(
+ broadcastReceiver,
+ delayedActionFilter,
+ SYSTEMUI_PERMISSION,
+ null /* scheduler */,
+ Context.RECEIVER_EXPORTED_UNAUDITED
+ )
+ }
+
+ /** Set an alarm for */
+ private fun setResetCanIgnoreAuthAlarm() {
+ val intent =
+ Intent(DELAYED_KEYGUARD_ACTION).apply {
+ setPackage(context.packageName)
+ putExtra(SEQ_EXTRA_KEY, timeoutCounter)
+ addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ }
+
+ val sender =
+ PendingIntent.getBroadcast(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val time = systemClock.elapsedRealtime() + getCanIgnoreAuthAndReturnToGoneDuration()
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, sender)
+
+ // TODO(b/346803756): Migrate support for child profiles.
+ }
+
+ /**
+ * Cancel the timeout by incrementing the counter so that we ignore the intent when it's
+ * received.
+ */
+ private fun cancelCanIgnoreAuthAlarm() {
+ timeoutCounter++
+ }
+
+ /**
+ * Whether pressing the power button locks the device immediately; vs. waiting for a specified
+ * timeout first.
+ */
+ private fun willLockImmediately(
+ userId: Int = selectedUserInteractor.getSelectedUserId()
+ ): Boolean {
+ return lockPatternUtils.getPowerButtonInstantlyLocks(userId) ||
+ !lockPatternUtils.isSecure(userId)
+ }
+
+ /**
+ * Returns whether the lockscreen is disabled, either because the keyguard service is disabled
+ * or because an adb command has disabled the lockscreen.
+ */
+ private fun isLockscreenDisabled(
+ userId: Int = selectedUserInteractor.getSelectedUserId()
+ ): Boolean {
+ return lockPatternUtils.isLockScreenDisabled(userId)
+ }
+
+ /**
+ * Returns the duration within which we can return to GONE without auth after a screen timeout
+ * (or power button press, if lock instantly is disabled).
+ *
+ * This takes into account the user's settings as well as device policy maximums.
+ */
+ private fun getCanIgnoreAuthAndReturnToGoneDuration(
+ userId: Int = selectedUserInteractor.getSelectedUserId()
+ ): Long {
+ // The timeout duration from settings (Security > Device Unlock > Gear icon > "Lock after
+ // screen timeout".
+ val durationSetting: Long =
+ secureSettings
+ .getIntForUser(
+ Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ KEYGUARD_CAN_IGNORE_AUTH_DURATION,
+ userId
+ )
+ .toLong()
+
+ // Device policy maximum timeout.
+ val durationDevicePolicyMax =
+ lockPatternUtils.devicePolicyManager.getMaximumTimeToLock(null, userId)
+
+ return if (durationDevicePolicyMax <= 0) {
+ durationSetting
+ } else {
+ var displayTimeout =
+ systemSettings
+ .getIntForUser(
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
+ userId
+ )
+ .toLong()
+
+ // Ignore negative values. I don't know why this would be negative, but this check has
+ // been around since 2016 and I see no upside to removing it.
+ displayTimeout = max(displayTimeout, 0)
+
+ // Respect the shorter of: the device policy (maximum duration between last user action
+ // and fully locking) or the "Lock after screen timeout" setting.
+ max(min(durationDevicePolicyMax - displayTimeout, durationSetting), 0)
+ }
+ }
+
+ companion object {
+ private const val DELAYED_KEYGUARD_ACTION =
+ "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"
+ private const val DELAYED_LOCK_PROFILE_ACTION =
+ "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_LOCK"
+ private const val SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"
+ private const val SEQ_EXTRA_KEY = "count"
+
+ private const val KEYGUARD_CAN_IGNORE_AUTH_DURATION = 5000
+ private const val KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 3355ffd..0985e69 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -21,14 +21,17 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAsleepInState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import dagger.Lazy
@@ -41,11 +44,13 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class WindowManagerLockscreenVisibilityInteractor
@Inject
constructor(
keyguardInteractor: KeyguardInteractor,
+ transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
fromLockscreenInteractor: FromLockscreenTransitionInteractor,
@@ -54,9 +59,15 @@
notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
sceneInteractor: Lazy<SceneInteractor>,
deviceEntryInteractor: Lazy<DeviceEntryInteractor>,
+ wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
) {
private val defaultSurfaceBehindVisibility =
- transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+ combine(
+ transitionInteractor.finishedKeyguardState,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
+ ) { finishedState, canWakeDirectlyToGone ->
+ isSurfaceVisible(finishedState) || canWakeDirectlyToGone
+ }
/**
* Surface visibility provided by the From*TransitionInteractor responsible for the currently
@@ -203,9 +214,13 @@
if (SceneContainerFlag.isEnabled) {
isDeviceNotEntered
} else {
- transitionInteractor.currentKeyguardState
- .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
- .map { (currentState, startedWithPrev) ->
+ combine(
+ transitionInteractor.currentKeyguardState,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
+ ::Pair
+ )
+ .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
+ .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
val startedFromStep = startedWithPrev.previousValue
val startedStep = startedWithPrev.newValue
val returningToGoneAfterCancellation =
@@ -213,16 +228,33 @@
startedFromStep.transitionState == TransitionState.CANCELED &&
startedFromStep.from == KeyguardState.GONE
- if (!returningToGoneAfterCancellation) {
- // By default, apply the lockscreen visibility of the current state.
- deviceEntryInteractor.get().isLockscreenEnabled() &&
- KeyguardState.lockscreenVisibleInState(currentState)
- } else {
- // If we're transitioning to GONE after a prior canceled transition from
- // GONE, then this is the camera launch transition from an asleep state back
- // to GONE. We don't want to show the lockscreen since we're aborting the
- // lock and going back to GONE.
+ val transitionInfo = transitionRepository.currentTransitionInfoInternal.value
+ val wakingDirectlyToGone =
+ deviceIsAsleepInState(transitionInfo.from) &&
+ transitionInfo.to == KeyguardState.GONE
+
+ if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
+ // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
+ // which means we never want to show the lockscreen throughout the
+ // transition. Same for waking directly to gone, due to the lockscreen being
+ // disabled or because the device was woken back up before the lock timeout
+ // duration elapsed.
KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
+ } else if (canWakeDirectlyToGone) {
+ // Never show the lockscreen if we can wake directly to GONE. This means
+ // that the lock timeout has not yet elapsed, or the keyguard is disabled.
+ // In either case, we don't show the activity lock screen until one of those
+ // conditions changes.
+ false
+ } else if (
+ currentState == KeyguardState.DREAMING &&
+ deviceEntryInteractor.get().isUnlocked.value
+ ) {
+ // Dreams dismiss keyguard and return to GONE if they can.
+ false
+ } else {
+ // Otherwise, use the visibility of the current state.
+ KeyguardState.lockscreenVisibleInState(currentState)
}
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 8f149fb..f96f053 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -37,6 +37,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.systemui.Flags.newAodTransition
@@ -80,6 +81,7 @@
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
@@ -110,6 +112,7 @@
vibratorHelper: VibratorHelper?,
falsingManager: FalsingManager?,
keyguardViewMediator: KeyguardViewMediator?,
+ mainImmediateDispatcher: CoroutineDispatcher,
): DisposableHandle {
val disposables = DisposableHandles()
val childViews = mutableMapOf<Int, View>()
@@ -128,6 +131,30 @@
val burnInParams = MutableStateFlow(BurnInParameters())
val viewState = ViewStateAccessor(alpha = { view.alpha })
+
+ disposables +=
+ view.repeatWhenAttached(mainImmediateDispatcher) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ if (MigrateClocksToBlueprint.isEnabled) {
+ launch("$TAG#topClippingBounds") {
+ val clipBounds = Rect()
+ viewModel.topClippingBounds.collect { clipTop ->
+ if (clipTop == null) {
+ view.setClipBounds(null)
+ } else {
+ clipBounds.apply {
+ top = clipTop
+ left = view.getLeft()
+ right = view.getRight()
+ bottom = view.getBottom()
+ }
+ view.setClipBounds(clipBounds)
+ }
+ }
+ }
+ }
+ }
+ }
disposables +=
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -192,23 +219,6 @@
}
launch {
- val clipBounds = Rect()
- viewModel.topClippingBounds.collect { clipTop ->
- if (clipTop == null) {
- view.setClipBounds(null)
- } else {
- clipBounds.apply {
- top = clipTop
- left = view.getLeft()
- right = view.getRight()
- bottom = view.getBottom()
- }
- view.setClipBounds(clipBounds)
- }
- }
- }
-
- launch {
viewModel.lockscreenStateAlpha(viewState).collect { alpha ->
childViews[statusViewId]?.alpha = alpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 4f0ac42..bc5b7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -394,6 +394,7 @@
null, // device entry haptics not required for preview mode
null, // falsing manager not required for preview mode
null, // keyguard view mediator is not required for preview mode
+ mainDispatcher,
)
}
rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index 6224170..83f694b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -104,7 +104,7 @@
private fun initScreenShareSpinner() {
val adapter = OptionsAdapter(dialog.context.applicationContext, screenShareOptions)
- screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_spinner)
+ screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_options)
screenShareModeSpinner.adapter = adapter
screenShareModeSpinner.onItemSelectedListener = this
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 8858041a..9ce8070 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,7 +30,7 @@
private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
private val onCancelClicked: Runnable,
private val appName: String?,
- private val forceShowPartialScreenshare: Boolean,
+ forceShowPartialScreenshare: Boolean,
hostUid: Int,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
index 75055668..776a8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
@@ -54,7 +54,10 @@
OTHER(isTouch = false, PowerManager.WAKE_REASON_UNKNOWN),
/** Device goes to sleep due to folding of a foldable device. */
- FOLD(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD);
+ FOLD(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD),
+
+ /** Device goes to sleep because it timed out. */
+ TIMEOUT(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
companion object {
fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
@@ -75,6 +78,7 @@
fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
return when (reason) {
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
+ PowerManager.GO_TO_SLEEP_REASON_TIMEOUT -> TIMEOUT
PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD -> FOLD
else -> OTHER
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 0bb4cfa..127ef84 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -22,6 +22,7 @@
import android.service.quicksettings.Tile.STATE_ACTIVE
import android.service.quicksettings.Tile.STATE_INACTIVE
import android.text.TextUtils
+import android.util.Log
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
@@ -81,7 +82,6 @@
import com.android.systemui.common.ui.compose.load
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toUiState
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -91,7 +91,6 @@
import java.util.function.Supplier
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.mapLatest
object TileType
@@ -103,29 +102,27 @@
showLabels: Boolean = false,
modifier: Modifier,
) {
- val state: TileUiState by
- tile.state
- .mapLatest { it.toUiState() }
- .collectAsStateWithLifecycle(tile.currentState.toUiState())
- val colors = TileDefaults.getColorForState(state.state)
+ val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
+ val uiState = remember(state) { state.toUiState() }
+ val colors = TileDefaults.getColorForState(uiState.state)
TileContainer(
colors = colors,
showLabels = showLabels,
- label = state.label.toString(),
+ label = uiState.label,
iconOnly = iconOnly,
clickEnabled = true,
onClick = tile::onClick,
onLongClick = tile::onLongClick,
modifier = modifier,
) {
- val icon = getTileIcon(icon = state.icon)
+ val icon = getTileIcon(icon = uiState.icon)
if (iconOnly) {
TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
} else {
LargeTileContent(
- label = state.label.toString(),
- secondaryLabel = state.secondaryLabel.toString(),
+ label = uiState.label,
+ secondaryLabel = uiState.secondaryLabel,
icon = icon,
colors = colors,
clickEnabled = true,
@@ -234,19 +231,26 @@
Text(
label,
color = colors.label,
- modifier = Modifier.basicMarquee(),
+ modifier = Modifier.tileMarquee(),
)
if (!TextUtils.isEmpty(secondaryLabel)) {
Text(
secondaryLabel ?: "",
color = colors.secondaryLabel,
- modifier = Modifier.basicMarquee(),
+ modifier = Modifier.tileMarquee(),
)
}
}
}
}
+private fun Modifier.tileMarquee(): Modifier {
+ return basicMarquee(
+ iterations = 1,
+ initialDelayMillis = 200,
+ )
+}
+
@Composable
fun TileLazyGrid(
modifier: Modifier = Modifier,
@@ -452,6 +456,7 @@
animateToEnd: Boolean = false,
modifier: Modifier = Modifier,
) {
+ Log.d("Fabian", "Recomposing tile icon")
val iconModifier = modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
val context = LocalContext.current
val loadedDrawable =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index b3acace..bb00494 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -26,8 +26,8 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
@@ -54,14 +54,24 @@
quickQuickSettingsRowInteractor.defaultRows
)
- val tileViewModels: Flow<List<SizedTile<TileViewModel>>> =
- columns.flatMapLatest { columns ->
- tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
- tiles
- .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
- .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
+ val tileViewModels: StateFlow<List<SizedTile<TileViewModel>>> =
+ columns
+ .flatMapLatest { columns ->
+ tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
+ tiles
+ .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+ .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
+ }
}
- }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ tilesInteractor.currentTiles.value
+ .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+ .let {
+ splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
+ }
+ )
private val TileSpec.width: Int
get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index 578a292..4ec59c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -16,20 +16,22 @@
package com.android.systemui.qs.panels.ui.viewmodel
+import androidx.compose.runtime.Immutable
import com.android.systemui.plugins.qs.QSTile
import java.util.function.Supplier
+@Immutable
data class TileUiState(
- val label: CharSequence,
- val secondaryLabel: CharSequence,
+ val label: String,
+ val secondaryLabel: String,
val state: Int,
val icon: Supplier<QSTile.Icon>,
)
fun QSTile.State.toUiState(): TileUiState {
return TileUiState(
- label ?: "",
- secondaryLabel ?: "",
+ label?.toString() ?: "",
+ secondaryLabel?.toString() ?: "",
state,
icon?.let { Supplier { icon } } ?: iconSupplier,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
index 7505b90..8578bb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.panels.ui.viewmodel
+import androidx.compose.runtime.Immutable
import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -25,6 +26,7 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
+@Immutable
class TileViewModel(private val tile: QSTile, val spec: TileSpec) {
val state: Flow<QSTile.State> =
conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4a4c73b..98a61df 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -74,7 +74,7 @@
when (intent?.action) {
ACTION_START -> {
bgExecutor.execute {
- traceurMessageSender.startTracing(issueRecordingState.traceType)
+ traceurMessageSender.startTracing(issueRecordingState.traceConfig)
}
issueRecordingState.isRecording = true
if (!issueRecordingState.recordScreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
index b077349..7612900 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
@@ -22,7 +22,8 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.traceur.TraceUtils.PresetTraceType
+import com.android.traceur.PresetTraceConfigs
+import com.android.traceur.TraceConfig
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
@@ -53,8 +54,8 @@
get() = prefs.getInt(KEY_ISSUE_TYPE_RES, ISSUE_TYPE_NOT_SET)
set(value) = prefs.edit().putInt(KEY_ISSUE_TYPE_RES, value).apply()
- val traceType: PresetTraceType
- get() = ALL_ISSUE_TYPES[issueTypeRes] ?: PresetTraceType.UNSET
+ val traceConfig: TraceConfig
+ get() = ALL_ISSUE_TYPES[issueTypeRes] ?: PresetTraceConfigs.getDefaultConfig()
private val listeners = CopyOnWriteArrayList<Runnable>()
@@ -83,12 +84,12 @@
const val KEY_ISSUE_TYPE_RES = "key_issueTypeRes"
const val ISSUE_TYPE_NOT_SET = -1
- val ALL_ISSUE_TYPES: Map<Int, PresetTraceType> =
+ val ALL_ISSUE_TYPES: Map<Int, TraceConfig?> =
hashMapOf(
- Pair(R.string.performance, PresetTraceType.PERFORMANCE),
- Pair(R.string.user_interface, PresetTraceType.UI),
- Pair(R.string.battery, PresetTraceType.BATTERY),
- Pair(R.string.thermal, PresetTraceType.THERMAL)
+ Pair(R.string.performance, PresetTraceConfigs.getPerformanceConfig()),
+ Pair(R.string.user_interface, PresetTraceConfigs.getUiConfig()),
+ Pair(R.string.battery, PresetTraceConfigs.getBatteryConfig()),
+ Pair(R.string.thermal, PresetTraceConfigs.getThermalConfig()),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index bbf4e51..8a51ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -87,7 +87,10 @@
setNegativeButton(R.string.cancel) { _, _ -> }
setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() }
}
- bgExecutor.execute { traceurMessageSender.bindToTraceur(dialog.context) }
+ bgExecutor.execute {
+ traceurMessageSender.onBoundToTraceur.add { traceurMessageSender.getTags() }
+ traceurMessageSender.bindToTraceur(dialog.context)
+ }
}
override fun createDialog(): SystemUIDialog = factory.create(this)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
index 51744aa..a31a9ef 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
@@ -35,7 +35,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.traceur.FileSender
import com.android.traceur.MessageConstants
-import com.android.traceur.TraceUtils.PresetTraceType
+import com.android.traceur.TraceConfig
import javax.inject.Inject
private const val TAG = "TraceurMessageSender"
@@ -45,11 +45,15 @@
private var binder: Messenger? = null
private var isBound: Boolean = false
+ val onBoundToTraceur = mutableListOf<Runnable>()
+
private val traceurConnection =
object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
binder = Messenger(service)
isBound = true
+ onBoundToTraceur.forEach(Runnable::run)
+ onBoundToTraceur.clear()
}
override fun onServiceDisconnected(className: ComponentName) {
@@ -93,9 +97,9 @@
}
@WorkerThread
- fun startTracing(traceType: PresetTraceType) {
+ fun startTracing(traceType: TraceConfig) {
val data =
- Bundle().apply { putSerializable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
+ Bundle().apply { putParcelable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
notifyTraceur(MessageConstants.START_WHAT, data)
}
@@ -103,11 +107,17 @@
@WorkerThread
fun shareTraces(context: Context, screenRecord: Uri?) {
- val replyHandler = Messenger(TraceurMessageHandler(context, screenRecord, backgroundLooper))
+ val replyHandler = Messenger(ShareFilesHandler(context, screenRecord, backgroundLooper))
notifyTraceur(MessageConstants.SHARE_WHAT, replyTo = replyHandler)
}
@WorkerThread
+ fun getTags() {
+ val replyHandler = Messenger(TagsHandler(backgroundLooper))
+ notifyTraceur(MessageConstants.TAGS_WHAT, replyTo = replyHandler)
+ }
+
+ @WorkerThread
private fun notifyTraceur(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) {
try {
binder!!.send(
@@ -122,7 +132,7 @@
}
}
- private class TraceurMessageHandler(
+ private class ShareFilesHandler(
private val context: Context,
private val screenRecord: Uri?,
looper: Looper,
@@ -154,4 +164,29 @@
context.startActivity(fileSharingIntent)
}
}
+
+ private class TagsHandler(looper: Looper) : Handler(looper) {
+
+ override fun handleMessage(msg: Message) {
+ if (MessageConstants.TAGS_WHAT == msg.what) {
+ val keys = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAGS)
+ val values =
+ msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS)
+ if (keys == null || values == null) {
+ throw IllegalArgumentException(
+ "Neither keys: $keys, nor values: $values can " + "be null"
+ )
+ }
+
+ val tags = keys.zip(values).map { "${it.first}: ${it.second}" }.toSet()
+ Log.e(
+ TAG,
+ "These tags: $tags will be saved and used for the Custom Trace" +
+ " Config dialog in a future CL. This log will be removed."
+ )
+ } else {
+ throw IllegalArgumentException("received unknown msg.what: " + msg.what)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index c87b1f5..95ee2e0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -20,7 +20,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.systemui.Flags.screenshotPrivateProfileAccessibilityAnnouncementFix;
-import static com.android.systemui.Flags.screenshotShelfUi2;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
@@ -407,22 +406,16 @@
}
final UUID requestId;
- if (screenshotShelfUi2()) {
- requestId = mActionsController.setCurrentScreenshot(screenshot);
- saveScreenshotInBackground(screenshot, requestId, finisher);
+ requestId = mActionsController.setCurrentScreenshot(screenshot);
+ saveScreenshotInBackground(screenshot, requestId, finisher);
- if (screenshot.getTaskId() >= 0) {
- mAssistContentRequester.requestAssistContent(
- screenshot.getTaskId(),
- assistContent ->
- mActionsController.onAssistContent(requestId, assistContent));
- } else {
- mActionsController.onAssistContent(requestId, null);
- }
+ if (screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(
+ screenshot.getTaskId(),
+ assistContent ->
+ mActionsController.onAssistContent(requestId, assistContent));
} else {
- requestId = UUID.randomUUID(); // passed through but unused for legacy UI
- saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
- this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+ mActionsController.onAssistContent(requestId, null);
}
// The window is focusable by default
@@ -458,9 +451,6 @@
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
- if (!screenshotShelfUi2()) {
- mScreenshotHandler.cancelTimeout(); // restarted after animation
- }
}
private boolean shouldShowUi() {
@@ -515,11 +505,7 @@
}
boolean isPendingSharedTransition() {
- if (screenshotShelfUi2()) {
- return mActionExecutor.isPendingSharedTransition();
- } else {
- return mViewProxy.isPendingSharedTransition();
- }
+ return mActionExecutor.isPendingSharedTransition();
}
// Any cleanup needed when the service is being destroyed.
@@ -603,11 +589,7 @@
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
// Hide the scroll chip until we know it's available in this
// orientation
- if (screenshotShelfUi2()) {
- mActionsController.onScrollChipInvalidated();
- } else {
- mViewProxy.hideScrollChip();
- }
+ mActionsController.onScrollChipInvalidated();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
mScreenshotHandler.postDelayed(
@@ -640,13 +622,8 @@
(response) -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
0, response.getPackageName());
- if (screenshotShelfUi2()) {
- mActionsController.onScrollChipReady(requestId,
- () -> onScrollButtonClicked(owner, response));
- } else {
- mViewProxy.showScrollChip(response.getPackageName(),
- () -> onScrollButtonClicked(owner, response));
- }
+ mActionsController.onScrollChipReady(requestId,
+ () -> onScrollButtonClicked(owner, response));
return Unit.INSTANCE;
}
);
@@ -715,11 +692,9 @@
mWindowManager.addView(decorView, mWindowLayoutParams);
decorView.requestApplyInsets();
- if (screenshotShelfUi2()) {
- ViewGroup layout = decorView.requireViewById(android.R.id.content);
- layout.setClipChildren(false);
- layout.setClipToPadding(false);
- }
+ ViewGroup layout = decorView.requireViewById(android.R.id.content);
+ layout.setClipChildren(false);
+ layout.setClipToPadding(false);
}
void removeWindow() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 8235325..56ba1af4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -16,15 +16,12 @@
package com.android.systemui.screenshot.dagger;
-import static com.android.systemui.Flags.screenshotShelfUi2;
-
import android.app.Service;
import android.view.accessibility.AccessibilityManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
-import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotShelfViewProxy;
@@ -96,14 +93,7 @@
return new ScreenshotViewModel(accessibilityManager);
}
- @Provides
- static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory(
- ScreenshotShelfViewProxy.Factory shelfScreenshotViewProxyFactory,
- LegacyScreenshotViewProxy.Factory legacyScreenshotViewProxyFactory) {
- if (screenshotShelfUi2()) {
- return shelfScreenshotViewProxyFactory;
- } else {
- return legacyScreenshotViewProxyFactory;
- }
- }
+ @Binds
+ abstract ScreenshotViewProxy.Factory bindScreenshotViewProxyFactory(
+ ScreenshotShelfViewProxy.Factory shelfScreenshotViewProxyFactory);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsLog.kt
new file mode 100644
index 0000000..ecb6286
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 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 com.android.systemui.statusbar.chips
+
+import javax.inject.Qualifier
+
+/** Logs for events related to the status bar chips. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class StatusBarChipsLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
new file mode 100644
index 0000000..173ff37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 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 com.android.systemui.statusbar.chips
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import dagger.Module
+import dagger.Provides
+
+@Module
+abstract class StatusBarChipsModule {
+ companion object {
+ @Provides
+ @SysUISingleton
+ @StatusBarChipsLog
+ fun provideChipsLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("StatusBarChips", 200)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
index 7e9acaf..eaefc11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
@@ -17,15 +17,36 @@
package com.android.systemui.statusbar.chips.call.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
/** Interactor for the ongoing phone call chip shown in the status bar. */
@SysUISingleton
class CallChipInteractor
@Inject
constructor(
+ @Application private val scope: CoroutineScope,
repository: OngoingCallRepository,
+ @StatusBarChipsLog private val logger: LogBuffer,
) {
- val ongoingCallState = repository.ongoingCallState
+ val ongoingCallState: StateFlow<OngoingCallModel> =
+ repository.ongoingCallState
+ .onEach {
+ logger.log(TAG, LogLevel.INFO, { str1 = it::class.simpleName }, { "State: $str1" })
+ }
+ .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall)
+
+ companion object {
+ private const val TAG = "OngoingCall"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index 21f301c..6917f46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -49,7 +49,7 @@
activeCastDevice
.map {
if (it != null) {
- MediaRouterCastModel.Casting
+ MediaRouterCastModel.Casting(deviceName = it.name)
} else {
MediaRouterCastModel.DoingNothing
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt
index b228922..1f84d7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt
@@ -21,6 +21,10 @@
/** MediaRouter isn't aware of any active cast. */
data object DoingNothing : MediaRouterCastModel
- /** MediaRouter has an active cast. */
- data object Casting : MediaRouterCastModel
+ /**
+ * MediaRouter has an active cast.
+ *
+ * @property deviceName the name of the device receiving the cast.
+ */
+ data class Casting(val deviceName: String?) : MediaRouterCastModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
index ffb20a7..cac3f25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
+import android.content.Context
import android.os.Bundle
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel.Companion.CAST_TO_OTHER_DEVICE_ICON
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
@@ -26,6 +28,7 @@
/** A dialog that lets the user stop an ongoing cast-screen-to-other-device event. */
class EndCastScreenToOtherDeviceDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ private val context: Context,
private val stopAction: () -> Unit,
private val state: ProjectionChipModel.Projecting,
) : SystemUIDialog.Delegate {
@@ -36,16 +39,8 @@
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
with(dialog) {
setIcon(CAST_TO_OTHER_DEVICE_ICON)
- setTitle(R.string.cast_screen_to_other_device_stop_dialog_title)
- // TODO(b/332662551): Include device name in this string.
- setMessage(
- endMediaProjectionDialogHelper.getDialogMessage(
- state.projectionState,
- genericMessageResId = R.string.cast_screen_to_other_device_stop_dialog_message,
- specificAppMessageResId =
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
- )
+ setTitle(R.string.cast_to_other_device_stop_dialog_title)
+ setMessage(getMessage())
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
@@ -54,4 +49,41 @@
}
}
}
+
+ private fun getMessage(): String {
+ return if (state.projectionState is MediaProjectionState.Projecting.SingleTask) {
+ val appBeingSharedName =
+ endMediaProjectionDialogHelper.getAppName(state.projectionState)
+ if (appBeingSharedName != null && state.deviceName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_specific_app_with_device,
+ appBeingSharedName,
+ state.deviceName,
+ )
+ } else if (appBeingSharedName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_specific_app,
+ appBeingSharedName,
+ )
+ } else if (state.deviceName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
+ state.deviceName
+ )
+ } else {
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
+ }
+ } else {
+ if (state.deviceName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_entire_screen_with_device,
+ state.deviceName
+ )
+ } else {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_entire_screen,
+ )
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
index afe67b4..7dc9b25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
+import android.content.Context
import android.os.Bundle
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel.Companion.CAST_TO_OTHER_DEVICE_ICON
@@ -29,6 +30,8 @@
*/
class EndGenericCastToOtherDeviceDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ private val context: Context,
+ private val deviceName: String?,
private val stopAction: () -> Unit,
) : SystemUIDialog.Delegate {
override fun createDialog(): SystemUIDialog {
@@ -36,11 +39,19 @@
}
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ val message =
+ if (deviceName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
+ deviceName,
+ )
+ } else {
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
+ }
with(dialog) {
setIcon(CAST_TO_OTHER_DEVICE_ICON)
setTitle(R.string.cast_to_other_device_stop_dialog_title)
- // TODO(b/332662551): Include device name in this string.
- setMessage(R.string.cast_to_other_device_stop_dialog_message)
+ setMessage(message)
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 2eff336..4183cdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
+import android.content.Context
import androidx.annotation.DrawableRes
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -53,6 +54,7 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ private val context: Context,
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
private val mediaRouterChipInteractor: MediaRouterChipInteractor,
private val systemClock: SystemClock,
@@ -115,7 +117,7 @@
// This does mean that the audio-only casting chip will *never* show a
// timer, because audio-only casting never activates the MediaProjection
// APIs and those are the only cast APIs that show a timer.
- createIconOnlyCastChip()
+ createIconOnlyCastChip(routerModel.deviceName)
}
}
}
@@ -178,7 +180,7 @@
)
}
- private fun createIconOnlyCastChip(): OngoingActivityChipModel.Shown {
+ private fun createIconOnlyCastChip(deviceName: String?): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.IconOnly(
icon =
Icon.Resource(
@@ -188,7 +190,7 @@
),
colors = ColorsModel.Red,
createDialogLaunchOnClickListener(
- createGenericCastToOtherDeviceDialogDelegate(),
+ createGenericCastToOtherDeviceDialogDelegate(deviceName),
dialogTransitionAnimator,
),
)
@@ -199,13 +201,16 @@
) =
EndCastScreenToOtherDeviceDialogDelegate(
endMediaProjectionDialogHelper,
+ context,
stopAction = this::stopProjecting,
state,
)
- private fun createGenericCastToOtherDeviceDialogDelegate() =
+ private fun createGenericCastToOtherDeviceDialogDelegate(deviceName: String?) =
EndGenericCastToOtherDeviceDialogDelegate(
endMediaProjectionDialogHelper,
+ context,
+ deviceName,
stopAction = this::stopMediaRouterCasting,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 20ebae7..ce60fab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -19,8 +19,11 @@
import android.content.pm.PackageManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.util.Utils
import javax.inject.Inject
@@ -45,12 +48,16 @@
@Application private val scope: CoroutineScope,
private val mediaProjectionRepository: MediaProjectionRepository,
private val packageManager: PackageManager,
+ @StatusBarChipsLog private val logger: LogBuffer,
) {
val projection: StateFlow<ProjectionChipModel> =
mediaProjectionRepository.mediaProjectionState
.map { state ->
when (state) {
- is MediaProjectionState.NotProjecting -> ProjectionChipModel.NotProjecting
+ is MediaProjectionState.NotProjecting -> {
+ logger.log(TAG, LogLevel.INFO, {}, { "State: NotProjecting" })
+ ProjectionChipModel.NotProjecting
+ }
is MediaProjectionState.Projecting -> {
val type =
if (isProjectionToOtherDevice(state.hostPackage)) {
@@ -58,7 +65,17 @@
} else {
ProjectionChipModel.Type.SHARE_TO_APP
}
- ProjectionChipModel.Projecting(type, state)
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = type.name
+ str2 = state.hostPackage
+ },
+ { "State: Projecting(type=$str1 hostPackage=$str2)" }
+ )
+ // TODO(b/351851835): Get the device name.
+ ProjectionChipModel.Projecting(type, state, deviceName = null)
}
}
}
@@ -81,4 +98,8 @@
// marked as going to a different device, even if that isn't always true. See b/321078669.
return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
}
+
+ companion object {
+ private const val TAG = "MediaProjection"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
index 85682f5..a1a5e82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
@@ -26,10 +26,16 @@
/** There is no media being projected. */
data object NotProjecting : ProjectionChipModel()
- /** Media is currently being projected. */
+ /**
+ * Media is currently being projected.
+ *
+ * @property deviceName the name of the device receiving the projection, or null if the
+ * projection is to this device (as opposed to a different device).
+ */
data class Projecting(
val type: Type,
val projectionState: MediaProjectionState.Projecting,
+ val deviceName: String?,
) : ProjectionChipModel()
enum class Type {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
index 402306a..6004365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
@@ -16,13 +16,8 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view
-import android.annotation.StringRes
import android.app.ActivityManager
-import android.content.Context
import android.content.pm.PackageManager
-import android.text.Html
-import android.text.Html.FROM_HTML_MODE_LEGACY
-import android.text.TextUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -35,63 +30,38 @@
constructor(
private val dialogFactory: SystemUIDialog.Factory,
private val packageManager: PackageManager,
- private val context: Context
) {
/** Creates a new [SystemUIDialog] using the given delegate. */
fun createDialog(delegate: SystemUIDialog.Delegate): SystemUIDialog {
return dialogFactory.create(delegate)
}
- /** See other [getDialogMessage]. */
- fun getDialogMessage(
- state: MediaProjectionState.Projecting,
- @StringRes genericMessageResId: Int,
- @StringRes specificAppMessageResId: Int,
- ): CharSequence {
+ fun getAppName(state: MediaProjectionState.Projecting): CharSequence? {
val specificTaskInfo =
if (state is MediaProjectionState.Projecting.SingleTask) {
state.task
} else {
null
}
- return getDialogMessage(specificTaskInfo, genericMessageResId, specificAppMessageResId)
+ return getAppName(specificTaskInfo)
+ }
+
+ fun getAppName(specificTaskInfo: ActivityManager.RunningTaskInfo?): CharSequence? {
+ val packageName = specificTaskInfo?.baseIntent?.component?.packageName ?: return null
+ return getAppName(packageName)
}
/**
- * Returns the message to show in the dialog based on the specific media projection state.
- *
- * @param genericMessageResId a res ID for a more generic "end projection" message
- * @param specificAppMessageResId a res ID for an "end projection" message that also lets us
- * specify which app is currently being projected.
+ * Returns the human-readable application name for the given package, or null if it couldn't be
+ * found for any reason.
*/
- fun getDialogMessage(
- specificTaskInfo: ActivityManager.RunningTaskInfo?,
- @StringRes genericMessageResId: Int,
- @StringRes specificAppMessageResId: Int,
- ): CharSequence {
- if (specificTaskInfo == null) {
- return context.getString(genericMessageResId)
- }
- val packageName =
- specificTaskInfo.baseIntent.component?.packageName
- ?: return context.getString(genericMessageResId)
+ fun getAppName(packageName: String): CharSequence? {
return try {
val appInfo = packageManager.getApplicationInfo(packageName, 0)
- val appName = appInfo.loadLabel(packageManager)
- getSpecificAppMessageText(specificAppMessageResId, appName)
+ appInfo.loadLabel(packageManager)
} catch (e: PackageManager.NameNotFoundException) {
// TODO(b/332662551): Log this error.
- context.getString(genericMessageResId)
+ null
}
}
-
- private fun getSpecificAppMessageText(
- @StringRes specificAppMessageResId: Int,
- appName: CharSequence,
- ): CharSequence {
- // https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML
- val escapedAppName = TextUtils.htmlEncode(appName.toString())
- val text = context.getString(specificAppMessageResId, escapedAppName)
- return Html.fromHtml(text, FROM_HTML_MODE_LEGACY)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
index 43b1d16..28a8385 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
@@ -18,10 +18,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.ScreenRecordRepository
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -39,6 +42,7 @@
@Application private val scope: CoroutineScope,
private val screenRecordRepository: ScreenRecordRepository,
private val mediaProjectionRepository: MediaProjectionRepository,
+ @StatusBarChipsLog private val logger: LogBuffer,
) {
val screenRecordState: StateFlow<ScreenRecordChipModel> =
// ScreenRecordRepository has the main "is the screen being recorded?" state, and
@@ -49,9 +53,19 @@
mediaProjectionRepository.mediaProjectionState,
) { screenRecordState, mediaProjectionState ->
when (screenRecordState) {
- is ScreenRecordModel.DoingNothing -> ScreenRecordChipModel.DoingNothing
- is ScreenRecordModel.Starting ->
+ is ScreenRecordModel.DoingNothing -> {
+ logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" })
+ ScreenRecordChipModel.DoingNothing
+ }
+ is ScreenRecordModel.Starting -> {
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ { long1 = screenRecordState.millisUntilStarted },
+ { "State: Starting($long1)" }
+ )
ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted)
+ }
is ScreenRecordModel.Recording -> {
val recordedTask =
if (
@@ -61,6 +75,12 @@
} else {
null
}
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = recordedTask?.baseIntent?.component?.packageName },
+ { "State: Recording(taskPackage=$str1)" }
+ )
ScreenRecordChipModel.Recording(recordedTask)
}
}
@@ -71,4 +91,8 @@
fun stopRecording() {
scope.launch { screenRecordRepository.stopRecording() }
}
+
+ companion object {
+ private const val TAG = "ScreenRecord"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
index 9adbff9..1eca827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.screenrecord.ui.view
import android.app.ActivityManager
+import android.content.Context
import android.os.Bundle
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -26,6 +27,7 @@
/** A dialog that lets the user stop an ongoing screen recording. */
class EndScreenRecordingDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ val context: Context,
private val stopAction: () -> Unit,
private val recordedTask: ActivityManager.RunningTaskInfo?,
) : SystemUIDialog.Delegate {
@@ -35,16 +37,18 @@
}
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ val appName = endMediaProjectionDialogHelper.getAppName(recordedTask)
+ val message =
+ if (appName != null) {
+ context.getString(R.string.screenrecord_stop_dialog_message_specific_app, appName)
+ } else {
+ context.getString(R.string.screenrecord_stop_dialog_message)
+ }
+
with(dialog) {
setIcon(ScreenRecordChipViewModel.ICON)
setTitle(R.string.screenrecord_stop_dialog_title)
- setMessage(
- endMediaProjectionDialogHelper.getDialogMessage(
- recordedTask,
- genericMessageResId = R.string.screenrecord_stop_dialog_message,
- specificAppMessageResId = R.string.screenrecord_stop_dialog_message_specific_app
- )
- )
+ setMessage(message)
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 53679f1..df25d57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
import android.app.ActivityManager
+import android.content.Context
import androidx.annotation.DrawableRes
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -47,6 +48,7 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ private val context: Context,
private val interactor: ScreenRecordChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
@@ -90,6 +92,7 @@
): EndScreenRecordingDialogDelegate {
return EndScreenRecordingDialogDelegate(
endMediaProjectionDialogHelper,
+ context,
stopAction = interactor::stopRecording,
recordedTask,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
index 7e7ef40..564f20e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.chips.sharetoapp.ui.view
+import android.content.Context
import android.os.Bundle
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -26,6 +28,7 @@
/** A dialog that lets the user stop an ongoing share-screen-to-app event. */
class EndShareToAppDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ private val context: Context,
private val stopAction: () -> Unit,
private val state: ProjectionChipModel.Projecting,
) : SystemUIDialog.Delegate {
@@ -37,13 +40,7 @@
with(dialog) {
setIcon(SHARE_TO_APP_ICON)
setTitle(R.string.share_to_app_stop_dialog_title)
- setMessage(
- endMediaProjectionDialogHelper.getDialogMessage(
- state.projectionState,
- genericMessageResId = R.string.share_to_app_stop_dialog_message,
- specificAppMessageResId = R.string.share_to_app_stop_dialog_message_specific_app
- )
- )
+ setMessage(getMessage())
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
@@ -52,4 +49,32 @@
}
}
}
+
+ private fun getMessage(): String {
+ return if (state.projectionState is MediaProjectionState.Projecting.SingleTask) {
+ // If a single app is being shared, use the name of the app being shared in the dialog
+ val appBeingSharedName =
+ endMediaProjectionDialogHelper.getAppName(state.projectionState)
+ if (appBeingSharedName != null) {
+ context.getString(
+ R.string.share_to_app_stop_dialog_message_single_app_specific,
+ appBeingSharedName,
+ )
+ } else {
+ context.getString(R.string.share_to_app_stop_dialog_message_single_app_generic)
+ }
+ } else {
+ // Otherwise, use the name of the app *receiving* the share
+ val hostAppName =
+ endMediaProjectionDialogHelper.getAppName(state.projectionState.hostPackage)
+ if (hostAppName != null) {
+ context.getString(
+ R.string.share_to_app_stop_dialog_message_entire_screen_with_host_app,
+ hostAppName
+ )
+ } else {
+ context.getString(R.string.share_to_app_stop_dialog_message_entire_screen)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 8aef5a4..c097720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
+import android.content.Context
import androidx.annotation.DrawableRes
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -48,6 +49,7 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ private val context: Context,
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
private val systemClock: SystemClock,
private val dialogTransitionAnimator: DialogTransitionAnimator,
@@ -97,6 +99,7 @@
private fun createShareToAppDialogDelegate(state: ProjectionChipModel.Projecting) =
EndShareToAppDialogDelegate(
endMediaProjectionDialogHelper,
+ context,
stopAction = this::stopProjecting,
state,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index e8d895c..40f86f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -21,8 +21,13 @@
/** Model representing the display of an ongoing activity as a chip in the status bar. */
sealed class OngoingActivityChipModel {
+ /** Condensed name representing the model, used for logs. */
+ abstract val logName: String
+
/** This chip shouldn't be shown. */
- data object Hidden : OngoingActivityChipModel()
+ data object Hidden : OngoingActivityChipModel() {
+ override val logName = "Hidden"
+ }
/** This chip should be shown with the given information. */
abstract class Shown(
@@ -42,7 +47,9 @@
override val icon: Icon,
override val colors: ColorsModel,
override val onClickListener: View.OnClickListener?,
- ) : Shown(icon, colors, onClickListener)
+ ) : Shown(icon, colors, onClickListener) {
+ override val logName = "Shown.Icon"
+ }
/** The chip shows a timer, counting up from [startTimeMs]. */
data class Timer(
@@ -59,7 +66,9 @@
*/
val startTimeMs: Long,
override val onClickListener: View.OnClickListener?,
- ) : Shown(icon, colors, onClickListener)
+ ) : Shown(icon, colors, onClickListener) {
+ override val logName = "Shown.Timer"
+ }
/**
* This chip shows a countdown using [secondsUntilStarted]. Used to inform users that an
@@ -69,6 +78,8 @@
override val colors: ColorsModel,
/** The number of seconds until an event is started. */
val secondsUntilStarted: Long,
- ) : Shown(icon = null, colors, onClickListener = null)
+ ) : Shown(icon = null, colors, onClickListener = null) {
+ override val logName = "Shown.Countdown"
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 9c8086f..15c348e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -18,6 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModel
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel
import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
@@ -45,6 +48,7 @@
shareToAppChipViewModel: ShareToAppChipViewModel,
castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel,
callChipViewModel: CallChipViewModel,
+ @StatusBarChipsLog private val logger: LogBuffer,
) {
/**
* A flow modeling the chip that should be shown in the status bar after accounting for possibly
@@ -60,6 +64,17 @@
castToOtherDeviceChipViewModel.chip,
callChipViewModel.chip,
) { screenRecord, shareToApp, castToOtherDevice, call ->
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = screenRecord.logName
+ str2 = shareToApp.logName
+ str3 = castToOtherDevice.logName
+ },
+ { "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." },
+ )
+ logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" })
// This `when` statement shows the priority order of the chips
when {
// Screen recording also activates the media projection APIs, so whenever the
@@ -76,4 +91,8 @@
// for those timers to get reset for any reason. So, as soon as any subscriber has
// requested the chip information, we need to maintain it forever. See b/347726238.
.stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+
+ companion object {
+ private const val TAG = "ChipsViewModel"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 0bb18d7..9240c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -469,10 +469,24 @@
get() = state
override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
- val collapsedEnough = event.fraction <= 0.9f
- if (collapsedEnough != this.collapsedEnoughToHide) {
- val couldShowPulsingHuns = canShowPulsingHuns
- this.collapsedEnoughToHide = collapsedEnough
+ val fraction = event.fraction
+
+ val wasCollapsedEnoughToHide = collapsedEnoughToHide
+ val isCollapsedEnoughToHide = fraction <= 0.9f
+
+ if (isCollapsedEnoughToHide != wasCollapsedEnoughToHide) {
+ val couldShowPulsingHuns = this.canShowPulsingHuns
+ this.collapsedEnoughToHide = isCollapsedEnoughToHide
+ val canShowPulsingHuns = this.canShowPulsingHuns
+
+ logger.logOnPanelExpansionChanged(
+ fraction,
+ wasCollapsedEnoughToHide,
+ isCollapsedEnoughToHide,
+ couldShowPulsingHuns,
+ canShowPulsingHuns
+ )
+
if (couldShowPulsingHuns && !canShowPulsingHuns) {
updateNotificationVisibility(animate = true, increaseSpeed = true)
headsUpManager.releaseAllImmediately()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index 9619bea..752ec2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -182,6 +182,31 @@
)
}
+ fun logOnPanelExpansionChanged(
+ fraction: Float,
+ wasCollapsedEnoughToHide: Boolean,
+ isCollapsedEnoughToHide: Boolean,
+ couldShowPulsingHuns: Boolean,
+ canShowPulsingHuns: Boolean
+ ) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ double1 = fraction.toDouble()
+ bool1 = wasCollapsedEnoughToHide
+ bool2 = isCollapsedEnoughToHide
+ bool3 = couldShowPulsingHuns
+ bool4 = canShowPulsingHuns
+ },
+ {
+ "onPanelExpansionChanged($double1):" +
+ " collapsedEnoughToHide: $bool1 -> $bool2," +
+ " canShowPulsingHuns: $bool3 -> $bool4"
+ }
+ )
+ }
+
fun logSetWakingUp(wakingUp: Boolean, requestDelayedAnimation: Boolean) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index fec7cf0..b63ee4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -18,6 +18,7 @@
import android.app.NotificationManager;
import android.content.Context;
+import android.os.Handler;
import android.service.notification.NotificationListenerService;
import com.android.internal.jank.InteractionJankMonitor;
@@ -283,9 +284,11 @@
Context context,
NotificationManager notificationManager,
@Application CoroutineScope coroutineScope,
- @Background CoroutineContext coroutineContext) {
+ @Background CoroutineContext coroutineContext,
+ @Background Handler handler
+ ) {
return new ZenModeRepositoryImpl(context, notificationManager,
- coroutineScope, coroutineContext);
+ coroutineScope, coroutineContext, handler);
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index cdab108..eebbb13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
+@file:OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
package com.android.systemui.statusbar.notification.domain.interactor
@@ -27,11 +27,15 @@
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
class HeadsUpNotificationInteractor
@Inject
@@ -73,10 +77,21 @@
val isHeadsUpOrAnimatingAway: Flow<Boolean> =
combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
- hasPinnedRows,
- animatingAway ->
- hasPinnedRows || animatingAway
- }
+ hasPinnedRows,
+ animatingAway ->
+ hasPinnedRows || animatingAway
+ }
+ .debounce { isHeadsUpOrAnimatingAway ->
+ if (isHeadsUpOrAnimatingAway) {
+ 0
+ } else {
+ // When the last pinned entry is removed from the [HeadsUpRepository],
+ // there might be a delay before the View starts animating.
+ 50L
+ }
+ }
+ .onStart { emit(false) } // emit false, so we don't wait for the initial update
+ .distinctUntilChanged()
private val canShowHeadsUp: Flow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 398c1d4..bd0097e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -74,9 +74,9 @@
mLightModeIconColorSingleTone = Color.WHITE;
} else {
mDarkModeIconColorSingleTone = context.getColor(
- com.android.settingslib.R.color.dark_mode_icon_color_single_tone);
+ com.android.settingslib.R.color.black);
mLightModeIconColorSingleTone = context.getColor(
- com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+ com.android.settingslib.R.color.white);
}
mTransitionsController = lightBarTransitionsControllerFactory.create(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 84e6018..d0a62e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -455,8 +455,8 @@
float luminance = Color.luminance(textColor);
@ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
luminance < 0.5
- ? com.android.settingslib.R.color.dark_mode_icon_color_single_tone
- : com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+ ? com.android.settingslib.R.color.black
+ : com.android.settingslib.R.color.white);
@ColorInt int contrastColor = luminance < 0.5
? DarkIconDispatcherImpl.DEFAULT_ICON_TINT
: DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT;
@@ -467,7 +467,7 @@
if (userSwitcherName != null) {
userSwitcherName.setTextColor(Utils.getColorStateListDefaultColor(
mContext,
- com.android.settingslib.R.color.light_mode_icon_color_single_tone));
+ com.android.settingslib.R.color.white));
}
if (iconManager != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 231a8c6..824415e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -39,7 +39,7 @@
) {
override fun toString(): String {
val appearanceString =
- ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
+ ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
return "LetterboxAppearance{$appearanceString, $appearanceRegions}"
}
}
@@ -57,14 +57,16 @@
private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
) : Dumpable {
- private val darkAppearanceIconColor = context.getColor(
- // For a dark background status bar, use a *light* icon color.
- com.android.settingslib.R.color.light_mode_icon_color_single_tone
- )
- private val lightAppearanceIconColor = context.getColor(
- // For a light background status bar, use a *dark* icon color.
- com.android.settingslib.R.color.dark_mode_icon_color_single_tone
- )
+ private val darkAppearanceIconColor =
+ context.getColor(
+ // For a dark background status bar, use a *light* icon color.
+ com.android.settingslib.R.color.white
+ )
+ private val lightAppearanceIconColor =
+ context.getColor(
+ // For a light background status bar, use a *dark* icon color.
+ com.android.settingslib.R.color.black
+ )
init {
dumpManager.registerCriticalDumpable(this)
@@ -85,7 +87,11 @@
lastAppearanceRegions = originalAppearanceRegions
lastLetterboxes = letterboxes
return getLetterboxAppearanceInternal(
- letterboxes, originalAppearance, originalAppearanceRegions, statusBarBounds)
+ letterboxes,
+ originalAppearance,
+ originalAppearanceRegions,
+ statusBarBounds
+ )
.also { lastLetterboxAppearance = it }
}
@@ -138,7 +144,9 @@
// full bounds of its window.
// Here we want the bounds to be only for the inner bounds of the letterboxed app.
AppearanceRegion(
- appearanceRegion.appearance, matchingLetterbox.letterboxInnerBounds)
+ appearanceRegion.appearance,
+ matchingLetterbox.letterboxInnerBounds
+ )
}
}
@@ -148,7 +156,8 @@
): LetterboxAppearance {
return LetterboxAppearance(
originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
- originalAppearanceRegions)
+ originalAppearanceRegions
+ )
}
@Appearance
@@ -215,7 +224,9 @@
lastAppearanceRegion: $lastAppearanceRegions,
lastLetterboxes: $lastLetterboxes,
lastLetterboxAppearance: $lastLetterboxAppearance
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0316b0e..96127b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -63,6 +63,7 @@
import com.android.systemui.bouncer.ui.BouncerView;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
@@ -167,6 +168,7 @@
private final BouncerView mPrimaryBouncerView;
private final Lazy<ShadeController> mShadeController;
private final Lazy<SceneInteractor> mSceneInteractorLazy;
+ private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractorLazy;
private Job mListenForAlternateBouncerTransitionSteps = null;
private Job mListenForKeyguardAuthenticatedBiometricsHandled = null;
@@ -395,7 +397,8 @@
Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor,
JavaAdapter javaAdapter,
Lazy<SceneInteractor> sceneInteractorLazy,
- StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor
+ StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor,
+ Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -430,6 +433,7 @@
mJavaAdapter = javaAdapter;
mSceneInteractorLazy = sceneInteractorLazy;
mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor;
+ mDeviceEntryInteractorLazy = deviceEntryInteractorLazy;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -735,6 +739,11 @@
* {@see KeyguardBouncer#show(boolean, boolean)}
*/
public void showBouncer(boolean scrimmed) {
+ if (SceneContainerFlag.isEnabled()) {
+ mDeviceEntryInteractorLazy.get().attemptDeviceEntry();
+ return;
+ }
+
if (DeviceEntryUdfpsRefactor.isEnabled()) {
if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) {
Log.d(TAG, "showBouncer:alternateBouncer.forceShow()");
@@ -777,8 +786,7 @@
hideAlternateBouncer(false);
if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
if (SceneContainerFlag.isEnabled()) {
- mSceneInteractorLazy.get().changeScene(
- Scenes.Bouncer, "StatusBarKeyguardViewManager.showPrimaryBouncer");
+ mDeviceEntryInteractorLazy.get().attemptDeviceEntry();
} else {
mPrimaryBouncerInteractor.show(scrimmed);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index 2c48487..f81b952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -18,7 +18,13 @@
import android.app.PendingIntent
-/** Represents the state of any ongoing calls. */
+/**
+ * Represents the state of any ongoing calls.
+ *
+ * TODO(b/332662551): If there's an ongoing call but the user has the call app open, then we use the
+ * NoCall model, *not* the InCall model, which is confusing when looking at the logs. We may want
+ * to make that more clear, either with better logging or different models.
+ */
sealed interface OngoingCallModel {
/** There is no ongoing call. */
data object NoCall : OngoingCallModel
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 64f8246..a58a264 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -27,6 +27,7 @@
import androidx.annotation.NonNull;
+import com.android.app.tracing.TraceStateLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
@@ -41,11 +42,11 @@
/**
* The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
* {@link ObservableServiceConnection}.
+ *
* @param <T> The transformed connection type handled by the service.
*/
public class PersistentConnectionManager<T> implements Dumpable {
private static final String TAG = "PersistentConnManager";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final SystemClock mSystemClock;
private final DelayableExecutor mBgExecutor;
@@ -55,6 +56,7 @@
private final Observer mObserver;
private final DumpManager mDumpManager;
private final String mDumpsysName;
+ private final TraceStateLogger mConnectionReasonLogger;
private int mReconnectAttempts = 0;
private Runnable mCurrentReconnectCancelable;
@@ -64,36 +66,52 @@
private final Runnable mConnectRunnable = new Runnable() {
@Override
public void run() {
+ mConnectionReasonLogger.log("ConnectionReasonRetry");
mCurrentReconnectCancelable = null;
mConnection.bind();
}
};
- private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
+ private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt(
+ "ConnectionReasonObserver");
private final ObservableServiceConnection.Callback<T> mConnectionCallback =
new ObservableServiceConnection.Callback<>() {
- private long mStartTime;
+ private long mStartTime = -1;
- @Override
- public void onConnected(ObservableServiceConnection connection, Object proxy) {
- mStartTime = mSystemClock.currentTimeMillis();
- }
+ @Override
+ public void onConnected(ObservableServiceConnection connection, Object proxy) {
+ mStartTime = mSystemClock.currentTimeMillis();
+ }
- @Override
- public void onDisconnected(ObservableServiceConnection connection, int reason) {
- // Do not attempt to reconnect if we were manually unbound
- if (reason == ObservableServiceConnection.DISCONNECT_REASON_UNBIND) {
- return;
- }
+ @Override
+ public void onDisconnected(ObservableServiceConnection connection, int reason) {
+ // Do not attempt to reconnect if we were manually unbound
+ if (reason == ObservableServiceConnection.DISCONNECT_REASON_UNBIND) {
+ return;
+ }
- if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
- initiateConnectionAttempt();
- } else {
- scheduleConnectionAttempt();
- }
- }
- };
+ if (mStartTime <= 0) {
+ Log.e(TAG, "onDisconnected called with invalid connection start time: "
+ + mStartTime);
+ return;
+ }
+
+ final float connectionDuration = mSystemClock.currentTimeMillis() - mStartTime;
+ // Reset the start time.
+ mStartTime = -1;
+
+ if (connectionDuration > mMinConnectionDuration) {
+ Log.i(TAG, "immediately reconnecting since service was connected for "
+ + connectionDuration
+ + "ms which is longer than the min duration of "
+ + mMinConnectionDuration + "ms");
+ initiateConnectionAttempt("ConnectionReasonMinDurationMet");
+ } else {
+ scheduleConnectionAttempt();
+ }
+ }
+ };
@Inject
public PersistentConnectionManager(
@@ -112,6 +130,7 @@
mObserver = observer;
mDumpManager = dumpManager;
mDumpsysName = TAG + "#" + dumpsysName;
+ mConnectionReasonLogger = new TraceStateLogger(mDumpsysName);
mMaxReconnectAttempts = maxReconnectAttempts;
mBaseReconnectDelayMs = baseReconnectDelayMs;
@@ -125,7 +144,7 @@
mDumpManager.registerCriticalDumpable(mDumpsysName, this);
mConnection.addCallback(mConnectionCallback);
mObserver.addCallback(mObserverCallback);
- initiateConnectionAttempt();
+ initiateConnectionAttempt("ConnectionReasonStart");
}
/**
@@ -140,6 +159,7 @@
/**
* Add a callback to the {@link ObservableServiceConnection}.
+ *
* @param callback The callback to add.
*/
public void addConnectionCallback(ObservableServiceConnection.Callback<T> callback) {
@@ -148,6 +168,7 @@
/**
* Remove a callback from the {@link ObservableServiceConnection}.
+ *
* @param callback The callback to remove.
*/
public void removeConnectionCallback(ObservableServiceConnection.Callback<T> callback) {
@@ -163,10 +184,10 @@
mConnection.dump(pw);
}
- private void initiateConnectionAttempt() {
+ private void initiateConnectionAttempt(String reason) {
+ mConnectionReasonLogger.log(reason);
// Reset attempts
mReconnectAttempts = 0;
-
// The first attempt is always a direct invocation rather than delayed.
mConnection.bind();
}
@@ -179,20 +200,15 @@
}
if (mReconnectAttempts >= mMaxReconnectAttempts) {
- if (DEBUG) {
- Log.d(TAG, "exceeded max connection attempts.");
- }
+ Log.d(TAG, "exceeded max connection attempts.");
return;
}
final long reconnectDelayMs =
(long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
- if (DEBUG) {
- Log.d(TAG,
- "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
- }
-
+ Log.d(TAG,
+ "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
mCurrentReconnectCancelable = mBgExecutor.executeDelayed(mConnectRunnable,
reconnectDelayMs);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
index 53bcf86..361e768 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
@@ -20,6 +20,7 @@
import android.hardware.input.InputManager
import android.hardware.input.InputManager.KeyboardBacklightListener
import android.hardware.input.KeyboardBacklightState
+import android.testing.TestableLooper
import android.view.InputDevice
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -27,11 +28,13 @@
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
import com.android.systemui.keyboard.data.model.Keyboard
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -53,6 +56,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4::class)
class KeyboardRepositoryTest : SysuiTestCase() {
@@ -63,6 +67,7 @@
private lateinit var underTest: KeyboardRepository
private lateinit var dispatcher: CoroutineDispatcher
+ private lateinit var inputDeviceRepo: InputDeviceRepository
private lateinit var testScope: TestScope
@Before
@@ -75,7 +80,9 @@
}
dispatcher = StandardTestDispatcher()
testScope = TestScope(dispatcher)
- underTest = KeyboardRepositoryImpl(testScope.backgroundScope, dispatcher, inputManager)
+ val handler = FakeHandler(TestableLooper.get(this).looper)
+ inputDeviceRepo = InputDeviceRepository(handler, testScope.backgroundScope, inputManager)
+ underTest = KeyboardRepositoryImpl(dispatcher, inputManager, inputDeviceRepo)
}
@Test
@@ -363,6 +370,7 @@
private val maxBrightnessLevel: Int
) : KeyboardBacklightState() {
override fun getBrightnessLevel() = brightnessLevel
+
override fun getMaxBrightnessLevel() = maxBrightnessLevel
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt
new file mode 100644
index 0000000..e49e2b49
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/AppCategoriesShortcutsSourceTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 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 com.android.systemui.keyboard.shortcut.data.source
+
+import android.content.Intent.CATEGORY_APP_BROWSER
+import android.content.Intent.CATEGORY_APP_CALCULATOR
+import android.content.Intent.CATEGORY_APP_CALENDAR
+import android.content.Intent.CATEGORY_APP_CONTACTS
+import android.content.Intent.CATEGORY_APP_EMAIL
+import android.content.Intent.CATEGORY_APP_MAPS
+import android.content.Intent.CATEGORY_APP_MESSAGING
+import android.content.Intent.CATEGORY_APP_MUSIC
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.icons.fakeAppCategoryIconProvider
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AppCategoriesShortcutsSourceTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val defaultAppIconsProvider = kosmos.fakeAppCategoryIconProvider
+ private val source = kosmos.shortcutHelperAppCategoriesShortcutsSource
+
+ @Before
+ fun setUp() {
+ categoryApps.forEach { categoryAppIcon ->
+ defaultAppIconsProvider.installCategoryApp(
+ categoryAppIcon.category,
+ categoryAppIcon.packageName,
+ categoryAppIcon.iconResId
+ )
+ }
+ }
+
+ @Test
+ fun shortcutGroups_returnsSingleGroup() =
+ testScope.runTest { assertThat(source.shortcutGroups(TEST_DEVICE_ID)).hasSize(1) }
+
+ @Test
+ fun shortcutGroups_hasAssistantIcon() =
+ testScope.runTest {
+ defaultAppIconsProvider.installAssistantApp(ASSISTANT_PACKAGE, ASSISTANT_ICON_RES_ID)
+
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutInfo = shortcuts.first { it.label == "Assistant" }
+
+ assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(ASSISTANT_PACKAGE)
+ assertThat(shortcutInfo.icon!!.resId).isEqualTo(ASSISTANT_ICON_RES_ID)
+ }
+
+ @Test
+ fun shortcutGroups_hasBrowserIcon() =
+ testScope.runTest {
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutInfo = shortcuts.first { it.label == "Browser" }
+
+ assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(BROWSER_PACKAGE)
+ assertThat(shortcutInfo.icon!!.resId).isEqualTo(BROWSER_ICON_RES_ID)
+ }
+
+ @Test
+ fun shortcutGroups_hasContactsIcon() =
+ testScope.runTest {
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutInfo = shortcuts.first { it.label == "Contacts" }
+
+ assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(CONTACTS_PACKAGE)
+ assertThat(shortcutInfo.icon!!.resId).isEqualTo(CONTACTS_ICON_RES_ID)
+ }
+
+ @Test
+ fun shortcutGroups_hasEmailIcon() =
+ testScope.runTest {
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutInfo = shortcuts.first { it.label == "Email" }
+
+ assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(EMAIL_PACKAGE)
+ assertThat(shortcutInfo.icon!!.resId).isEqualTo(EMAIL_ICON_RES_ID)
+ }
+
+ @Test
+ fun shortcutGroups_hasCalendarIcon() =
+ testScope.runTest {
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutInfo = shortcuts.first { it.label == "Calendar" }
+
+ assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(CALENDAR_PACKAGE)
+ assertThat(shortcutInfo.icon!!.resId).isEqualTo(CALENDAR_ICON_RES_ID)
+ }
+
+ @Test
+ fun shortcutGroups_hasMapsIcon() =
+ testScope.runTest {
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutInfo = shortcuts.first { it.label == "Maps" }
+
+ assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(MAPS_PACKAGE)
+ assertThat(shortcutInfo.icon!!.resId).isEqualTo(MAPS_ICON_RES_ID)
+ }
+
+ @Test
+ fun shortcutGroups_hasMessagingIcon() =
+ testScope.runTest {
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutInfo = shortcuts.first { it.label == "SMS" }
+
+ assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(MESSAGING_PACKAGE)
+ assertThat(shortcutInfo.icon!!.resId).isEqualTo(MESSAGING_ICON_RES_ID)
+ }
+
+ @Test
+ fun shortcutGroups_hasMusicIcon() =
+ testScope.runTest {
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutInfo = shortcuts.first { it.label == "Music" }
+
+ assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(MUSIC_PACKAGE)
+ assertThat(shortcutInfo.icon!!.resId).isEqualTo(MUSIC_ICON_RES_ID)
+ }
+
+ @Test
+ fun shortcutGroups_hasCalculatorIcon() =
+ testScope.runTest {
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutInfo = shortcuts.first { it.label == "Calculator" }
+
+ assertThat(shortcutInfo.icon!!.resPackage).isEqualTo(CALCULATOR_PACKAGE)
+ assertThat(shortcutInfo.icon!!.resId).isEqualTo(CALCULATOR_ICON_RES_ID)
+ }
+
+ @Test
+ fun shortcutGroups_shortcutsSortedByLabelIgnoringCase() =
+ testScope.runTest {
+ val shortcuts = source.shortcutGroups(TEST_DEVICE_ID).first().items
+
+ val shortcutLabels = shortcuts.map { it.label!!.toString() }
+ assertThat(shortcutLabels).isEqualTo(shortcutLabels.sortedBy { it.lowercase() })
+ }
+
+ @Test
+ fun shortcutGroups_noAssistantApp_excludesAssistantFromShortcuts() =
+ testScope.runTest {
+ val shortcutLabels =
+ source.shortcutGroups(TEST_DEVICE_ID).first().items.map { it.label!!.toString() }
+
+ assertThat(shortcutLabels).doesNotContain("Assistant")
+ }
+
+ private companion object {
+ private const val ASSISTANT_PACKAGE = "the.assistant.app"
+ private const val ASSISTANT_ICON_RES_ID = 123
+
+ private const val BROWSER_PACKAGE = "com.test.browser"
+ private const val BROWSER_ICON_RES_ID = 1
+
+ private const val CONTACTS_PACKAGE = "app.test.contacts"
+ private const val CONTACTS_ICON_RES_ID = 234
+
+ private const val EMAIL_PACKAGE = "email.app.test"
+ private const val EMAIL_ICON_RES_ID = 351
+
+ private const val CALENDAR_PACKAGE = "app.test.calendar"
+ private const val CALENDAR_ICON_RES_ID = 411
+
+ private const val MAPS_PACKAGE = "maps.app.package"
+ private const val MAPS_ICON_RES_ID = 999
+
+ private const val MUSIC_PACKAGE = "com.android.music"
+ private const val MUSIC_ICON_RES_ID = 101
+
+ private const val MESSAGING_PACKAGE = "my.sms.app"
+ private const val MESSAGING_ICON_RES_ID = 9191
+
+ private const val CALCULATOR_PACKAGE = "that.calculator.app"
+ private const val CALCULATOR_ICON_RES_ID = 314
+
+ private val categoryApps =
+ listOf(
+ CategoryApp(CATEGORY_APP_BROWSER, BROWSER_PACKAGE, BROWSER_ICON_RES_ID),
+ CategoryApp(CATEGORY_APP_CONTACTS, CONTACTS_PACKAGE, CONTACTS_ICON_RES_ID),
+ CategoryApp(CATEGORY_APP_EMAIL, EMAIL_PACKAGE, EMAIL_ICON_RES_ID),
+ CategoryApp(CATEGORY_APP_CALENDAR, CALENDAR_PACKAGE, CALENDAR_ICON_RES_ID),
+ CategoryApp(CATEGORY_APP_MAPS, MAPS_PACKAGE, MAPS_ICON_RES_ID),
+ CategoryApp(CATEGORY_APP_MUSIC, MUSIC_PACKAGE, MUSIC_ICON_RES_ID),
+ CategoryApp(CATEGORY_APP_MESSAGING, MESSAGING_PACKAGE, MESSAGING_ICON_RES_ID),
+ CategoryApp(CATEGORY_APP_CALCULATOR, CALCULATOR_PACKAGE, CALCULATOR_ICON_RES_ID),
+ )
+
+ private const val TEST_DEVICE_ID = 123
+ }
+
+ private class CategoryApp(val category: String, val packageName: String, val iconResId: Int)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index a5f1f8c..4c1e869 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
@@ -47,12 +48,14 @@
private val systemShortcutsSource = FakeKeyboardShortcutGroupsSource()
private val multitaskingShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ private val defaultAppsShortcutsSource = FakeKeyboardShortcutGroupsSource()
@OptIn(ExperimentalCoroutinesApi::class)
private val kosmos =
testKosmos().also {
it.testDispatcher = UnconfinedTestDispatcher()
it.shortcutHelperSystemShortcutsSource = systemShortcutsSource
it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource
+ it.shortcutHelperAppCategoriesShortcutsSource = defaultAppsShortcutsSource
}
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 42ab25f..032794c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -56,6 +56,7 @@
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
@@ -97,6 +98,7 @@
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @Ignore("Until b/349837588 is fixed")
fun testTransitionToOccluded_ifDreamEnds_occludingActivityOnTop() =
testScope.runTest {
kosmos.fakeKeyguardRepository.setDreaming(true)
@@ -156,6 +158,7 @@
reset(transitionRepository)
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ kosmos.fakeKeyguardRepository.setDreaming(false)
runCurrent()
assertThat(transitionRepository)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
index 459e41d..ea5a41f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
@@ -18,25 +18,22 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.inWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.mockTopActivityClassName
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.system.ActivityManagerWrapper
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
+import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.testKosmos
import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -49,10 +46,16 @@
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
- private lateinit var underTest: InWindowLauncherUnlockAnimationInteractor
-
- private lateinit var testComponent: TestComponent
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val underTest =
+ InWindowLauncherUnlockAnimationInteractor(
+ kosmos.inWindowLauncherUnlockAnimationRepository,
+ kosmos.applicationCoroutineScope,
+ kosmos.keyguardTransitionInteractor,
+ { kosmos.keyguardSurfaceBehindRepository },
+ kosmos.activityManagerWrapper,
+ )
+ private val testScope = kosmos.testScope
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
@Mock private lateinit var activityManagerWrapper: ActivityManagerWrapper
@@ -62,19 +65,9 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- testComponent =
- DaggerInWindowLauncherUnlockAnimationInteractorTest_TestComponent.factory()
- .create(
- test = this,
- mocks =
- TestMocksModule(
- activityManagerWrapper = activityManagerWrapper,
- ),
- )
- underTest = testComponent.underTest
- testScope = testComponent.testScope
- transitionRepository = testComponent.transitionRepository
+ transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ activityManagerWrapper = kosmos.activityManagerWrapper
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
}
@@ -92,7 +85,7 @@
)
// Put launcher on top
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -175,7 +168,7 @@
)
// Put not launcher on top
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName("not_launcher")
@@ -252,7 +245,7 @@
)
// Put launcher on top
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -296,7 +289,7 @@
)
// Put Launcher on top and begin transitioning to GONE.
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -316,7 +309,7 @@
values
)
- testComponent.surfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
+ kosmos.keyguardSurfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
runCurrent()
assertEquals(
@@ -360,7 +353,7 @@
)
// Put Launcher on top and begin transitioning to GONE.
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -402,7 +395,7 @@
)
// Put Launcher on top and begin transitioning to GONE.
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -427,7 +420,7 @@
to = KeyguardState.AOD,
)
)
- testComponent.surfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
+ kosmos.keyguardSurfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
runCurrent()
assertEquals(
@@ -437,29 +430,4 @@
values
)
}
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent {
- val underTest: InWindowLauncherUnlockAnimationInteractor
- val testScope: TestScope
- val transitionRepository: FakeKeyguardTransitionRepository
- val surfaceBehindRepository: FakeKeyguardSurfaceBehindRepository
- val inWindowLauncherUnlockAnimationRepository: InWindowLauncherUnlockAnimationRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- ): TestComponent
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
new file mode 100644
index 0000000..22181f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 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 com.android.systemui.keyguard.domain.interactor
+
+import android.app.AlarmManager
+import android.app.admin.alarmManager
+import android.app.admin.devicePolicyManager
+import android.content.BroadcastReceiver
+import android.content.Intent
+import android.content.mockedContext
+import android.os.PowerManager
+import android.os.UserHandle
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
+
+ private var lastRegisteredBroadcastReceiver: BroadcastReceiver? = null
+ private val kosmos =
+ testKosmos().apply {
+ whenever(mockedContext.user).thenReturn(mock<UserHandle>())
+ doAnswer { invocation ->
+ lastRegisteredBroadcastReceiver = invocation.arguments[0] as BroadcastReceiver
+ }
+ .whenever(mockedContext)
+ .registerReceiver(any(), any(), any(), any(), any())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.keyguardWakeDirectlyToGoneInteractor
+ private val lockPatternUtils = kosmos.lockPatternUtils
+ private val repository = kosmos.fakeKeyguardRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Test
+ fun testCanWakeDirectlyToGone_keyguardServiceEnabledThenDisabled() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ repository.setKeyguardEnabled(false)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // Default to false.
+ true, // True now that keyguard service is disabled
+ ),
+ canWake
+ )
+
+ repository.setKeyguardEnabled(true)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false,
+ ),
+ canWake
+ )
+ }
+
+ @Test
+ fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ // Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to
+ // update on the next wake/sleep event.
+ false,
+ ),
+ canWake
+ )
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ // True since we slept after setting isLockScreenDisabled=true
+ true,
+ ),
+ canWake
+ )
+
+ kosmos.powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ ),
+ canWake
+ )
+
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ kosmos.powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false,
+ ),
+ canWake
+ )
+ }
+
+ @Test
+ fun testCanWakeDirectlyToGone_wakeAndUnlock() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ repository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
+ runCurrent()
+
+ assertEquals(listOf(false, true), canWake)
+
+ repository.setBiometricUnlockState(BiometricUnlockMode.NONE)
+ runCurrent()
+
+ assertEquals(listOf(false, true, false), canWake)
+ }
+
+ @Test
+ fun testCanWakeDirectlyToGone_andSetsAlarm_ifPowerButtonDoesNotLockImmediately() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ repository.setCanIgnoreAuthAndReturnToGone(true)
+ runCurrent()
+
+ assertEquals(listOf(false, true), canWake)
+
+ repository.setCanIgnoreAuthAndReturnToGone(false)
+ runCurrent()
+
+ assertEquals(listOf(false, true, false), canWake)
+ }
+
+ @Test
+ fun testSetsCanIgnoreAuth_andSetsAlarm_whenTimingOut() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
+ .thenReturn(-1)
+ kosmos.fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, 500)
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ ),
+ canWake
+ )
+
+ verify(kosmos.alarmManager)
+ .setExactAndAllowWhileIdle(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+ anyLong(),
+ any(),
+ )
+ }
+
+ @Test
+ fun testCancelsFirstAlarm_onWake_withSecondAlarmSet() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
+ .thenReturn(-1)
+ kosmos.fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, 500)
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT
+ )
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ // Timed out, so we can ignore auth/return to GONE.
+ true,
+ ),
+ canWake
+ )
+
+ verify(kosmos.alarmManager)
+ .setExactAndAllowWhileIdle(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+ anyLong(),
+ any(),
+ )
+
+ kosmos.powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ // Should be canceled by the wakeup, but there would still be an
+ // alarm in flight that should be canceled.
+ false,
+ ),
+ canWake
+ )
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false,
+ // Back to sleep.
+ true,
+ ),
+ canWake
+ )
+
+ // Simulate the first sleep's alarm coming in.
+ lastRegisteredBroadcastReceiver?.onReceive(
+ kosmos.mockedContext,
+ Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD")
+ )
+ runCurrent()
+
+ // It should not have any effect.
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ ),
+ canWake
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
index c7f4416..0cfc20d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
@@ -18,17 +18,15 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.inWindowLauncherUnlockAnimationInteractor
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
+import com.android.systemui.keyguard.ui.viewmodel.InWindowLauncherAnimationViewModel
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import dagger.BindsInstance
-import dagger.Component
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -45,10 +43,9 @@
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class InWindowLauncherUnlockAnimationManagerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
private lateinit var underTest: InWindowLauncherUnlockAnimationManager
-
- private lateinit var testComponent: TestComponent
- private lateinit var testScope: TestScope
+ private val testScope = kosmos.testScope
@Mock private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController
@@ -56,14 +53,14 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- testComponent =
- DaggerInWindowLauncherUnlockAnimationManagerTest_TestComponent.factory()
- .create(
- test = this,
- )
- underTest = testComponent.underTest
- testScope = testComponent.testScope
-
+ underTest =
+ InWindowLauncherUnlockAnimationManager(
+ kosmos.inWindowLauncherUnlockAnimationInteractor,
+ InWindowLauncherAnimationViewModel(
+ kosmos.inWindowLauncherUnlockAnimationInteractor
+ ),
+ kosmos.applicationCoroutineScope
+ )
underTest.setLauncherUnlockController("launcherClass", launcherUnlockAnimationController)
}
@@ -114,25 +111,4 @@
verifyNoMoreInteractions(launcherUnlockAnimationController)
}
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent {
- val underTest: InWindowLauncherUnlockAnimationManager
- val testScope: TestScope
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- ): TestComponent
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
index 548366e..d183c73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -79,7 +79,7 @@
val overrideDisableSingleAppOption = false
setUpAndShowDialog(overrideDisableSingleAppOption)
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
val secondOptionText =
spinner.adapter
.getDropDownView(1, null, spinner)
@@ -100,7 +100,7 @@
val overrideDisableSingleAppOption = true
setUpAndShowDialog(overrideDisableSingleAppOption)
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
val secondOptionText =
spinner.adapter
.getDropDownView(1, null, spinner)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
index 3621ab9..b0265c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
@@ -103,7 +103,7 @@
public void setIsLightsOut_AutoDim() {
mTransitions.setAutoDim(true);
- assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_OPAQUE));
+ assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT));
assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_LIGHTS_OUT));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index db607fd..cc8d7d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -109,7 +109,7 @@
fun testShowDialog_partialScreenSharingEnabled_optionsSpinnerIsVisible() {
showDialog()
- val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner).visibility
+ val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options).visibility
assertThat(visibility).isEqualTo(View.VISIBLE)
}
@@ -155,7 +155,7 @@
fun showDialog_singleAppIsDefault() {
showDialog()
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app)
assertEquals(spinner.adapter.getItem(0), singleApp)
}
@@ -217,7 +217,7 @@
}
private fun onSpinnerItemSelected(position: Int) {
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
checkNotNull(spinner.onItemSelectedListener)
.onItemSelected(spinner, mock(), position, /* id= */ 0)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 69e8f47..9e6a498 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -6,26 +6,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.ExpandHelper
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.plugins.qs.QS
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -33,17 +30,16 @@
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
-import dagger.BindsInstance
-import dagger.Component
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
@@ -65,8 +61,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -77,15 +73,14 @@
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
-
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+ }
private lateinit var transitionController: LockscreenShadeTransitionController
- private lateinit var testComponent: TestComponent
- private val configurationController
- get() = testComponent.configurationController
- private val disableFlagsRepository
- get() = testComponent.disableFlagsRepository
- private val testScope
- get() = testComponent.testScope
+ private val configurationController = kosmos.fakeConfigurationController
+ private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
+ private val testScope = kosmos.testScope
private val qsSceneAdapter = FakeQSSceneAdapter({ mock() })
@@ -134,26 +129,6 @@
whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
whenever(naturalScrollingSettingObserver.isNaturalScrollingEnabled).thenReturn(true)
- testComponent =
- DaggerLockscreenShadeTransitionControllerTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- },
- mocks =
- TestMocksModule(
- notificationShadeDepthController = depthController,
- keyguardBypassController = keyguardBypassController,
- mediaHierarchyManager = mediaHierarchyManager,
- notificationLockscreenUserManager = lockScreenUserManager,
- notificationStackScrollLayoutController = nsslController,
- scrimController = scrimController,
- statusBarStateController = statusbarStateController,
- )
- )
-
transitionController =
LockscreenShadeTransitionController(
statusBarStateController = statusbarStateController,
@@ -191,10 +166,10 @@
falsingManager = FalsingManagerFake(),
dumpManager = mock(),
qsTransitionControllerFactory = { qsTransitionController },
- shadeRepository = testComponent.shadeRepository,
- shadeInteractor = testComponent.shadeInteractor,
+ shadeRepository = kosmos.shadeRepository,
+ shadeInteractor = kosmos.shadeInteractor,
splitShadeStateController = ResourcesSplitShadeStateController(),
- shadeLockscreenInteractorLazy = {shadeLockscreenInteractor},
+ shadeLockscreenInteractorLazy = { shadeLockscreenInteractor },
naturalScrollingSettingObserver = naturalScrollingSettingObserver,
lazyQSSceneAdapter = { qsSceneAdapter }
)
@@ -214,387 +189,424 @@
}
@Test
- fun testCantDragDownWhenQSExpanded() {
- assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
- whenever(qS.isFullyCollapsed).thenReturn(false)
- assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
- }
+ fun testCantDragDownWhenQSExpanded() =
+ testScope.runTest {
+ assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
+ whenever(qS.isFullyCollapsed).thenReturn(false)
+ assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
+ }
@Test
- fun testCanDragDownInLockedDownShade() {
- whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
- assertFalse("Can drag down in shade locked", transitionController.canDragDown())
- whenever(nsslController.isInLockedDownShade).thenReturn(true)
- assertTrue("Can't drag down in locked down shade", transitionController.canDragDown())
- }
+ fun testCanDragDownInLockedDownShade() =
+ testScope.runTest {
+ whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+ assertFalse("Can drag down in shade locked", transitionController.canDragDown())
+ whenever(nsslController.isInLockedDownShade).thenReturn(true)
+ assertTrue("Can't drag down in locked down shade", transitionController.canDragDown())
+ }
@Test
- fun testGoingToLockedShade() {
- transitionController.goToLockedShade(null)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- }
+ fun testGoingToLockedShade() =
+ testScope.runTest {
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ }
@Test
- fun testWakingToShadeLockedWhenDozing() {
- whenever(statusbarStateController.isDozing).thenReturn(true)
- transitionController.goToLockedShade(null)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- assertTrue("Not waking to shade locked", transitionController.isWakingToShadeLocked)
- }
+ fun testWakingToShadeLockedWhenDozing() =
+ testScope.runTest {
+ whenever(statusbarStateController.isDozing).thenReturn(true)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ assertTrue("Not waking to shade locked", transitionController.isWakingToShadeLocked)
+ }
@Test
- fun testNotWakingToShadeLockedWhenNotDozing() {
- whenever(statusbarStateController.isDozing).thenReturn(false)
- transitionController.goToLockedShade(null)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- assertFalse(
- "Waking to shade locked when not dozing",
- transitionController.isWakingToShadeLocked
- )
- }
-
- @Test
- fun testGoToLockedShadeOnlyOnKeyguard() {
- whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
- transitionController.goToLockedShade(null)
- whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE)
- transitionController.goToLockedShade(null)
- verify(statusbarStateController, never()).setState(anyInt())
- }
-
- @Test
- fun testDontGoWhenShadeDisabled() {
- disableFlagsRepository.disableFlags.value =
- DisableFlagsModel(
- disable2 = DISABLE2_NOTIFICATION_SHADE,
+ fun testNotWakingToShadeLockedWhenNotDozing() =
+ testScope.runTest {
+ whenever(statusbarStateController.isDozing).thenReturn(false)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ assertFalse(
+ "Waking to shade locked when not dozing",
+ transitionController.isWakingToShadeLocked
)
- testScope.runCurrent()
- transitionController.goToLockedShade(null)
- verify(statusbarStateController, never()).setState(anyInt())
- }
+ }
@Test
- fun testUserExpandsViewOnGoingToFullShade() {
- assertFalse("Row shouldn't be user expanded yet", row.isUserExpanded)
- transitionController.goToLockedShade(row)
- assertTrue("Row wasn't user expanded on drag down", row.isUserExpanded)
- }
+ fun testGoToLockedShadeOnlyOnKeyguard() =
+ testScope.runTest {
+ whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+ transitionController.goToLockedShade(null)
+ whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController, never()).setState(anyInt())
+ }
@Test
- fun testTriggeringBouncerNoNotificationsOnLockscreen() {
- whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
- transitionController.goToLockedShade(null)
- verify(statusbarStateController, never()).setState(anyInt())
- verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true)
- verify(centralSurfaces).showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject())
- }
+ fun testDontGoWhenShadeDisabled() =
+ testScope.runTest {
+ disableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(
+ disable2 = DISABLE2_NOTIFICATION_SHADE,
+ )
+ testScope.runCurrent()
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController, never()).setState(anyInt())
+ }
@Test
- fun testGoToLockedShadeCreatesQSAnimation() {
- transitionController.goToLockedShade(null)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
- assertNotNull(transitionController.dragDownAnimator)
- }
+ fun testUserExpandsViewOnGoingToFullShade() =
+ testScope.runTest {
+ assertFalse("Row shouldn't be user expanded yet", row.isUserExpanded)
+ transitionController.goToLockedShade(row)
+ assertTrue("Row wasn't user expanded on drag down", row.isUserExpanded)
+ }
@Test
- fun testGoToLockedShadeDoesntCreateQSAnimation() {
- transitionController.goToLockedShade(null, needsQSAnimation = false)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
- assertNull(transitionController.dragDownAnimator)
- }
+ fun testTriggeringBouncerNoNotificationsOnLockscreen() =
+ testScope.runTest {
+ whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController, never()).setState(anyInt())
+ verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true)
+ verify(centralSurfaces)
+ .showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject())
+ }
@Test
- fun testGoToLockedShadeAlwaysCreatesQSAnimationInSplitShade() {
- enableSplitShade()
- transitionController.goToLockedShade(null, needsQSAnimation = true)
- verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
- assertNotNull(transitionController.dragDownAnimator)
- }
+ fun testGoToLockedShadeCreatesQSAnimation() =
+ testScope.runTest {
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
+ assertNotNull(transitionController.dragDownAnimator)
+ }
@Test
- fun testGoToLockedShadeCancelDoesntLeaveShadeOpenOnKeyguardHide() {
- whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
- whenever(lockScreenUserManager.isLockscreenPublicMode(any())).thenReturn(true)
- transitionController.goToLockedShade(null)
- val captor = argumentCaptor<Runnable>()
- verify(centralSurfaces).showBouncerWithDimissAndCancelIfKeyguard(isNull(), captor.capture())
- captor.value.run()
- verify(statusbarStateController).setLeaveOpenOnKeyguardHide(false)
- }
+ fun testGoToLockedShadeDoesntCreateQSAnimation() =
+ testScope.runTest {
+ transitionController.goToLockedShade(null, needsQSAnimation = false)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
+ assertNull(transitionController.dragDownAnimator)
+ }
@Test
- fun testDragDownAmountDoesntCallOutInLockedDownShade() {
- whenever(nsslController.isInLockedDownShade).thenReturn(true)
- transitionController.dragDownAmount = 10f
- verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
- verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
- verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
- verify(transitionControllerCallback, never())
- .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
- verify(qsTransitionController, never()).dragDownAmount = anyFloat()
- }
+ fun testGoToLockedShadeAlwaysCreatesQSAnimationInSplitShade() =
+ testScope.runTest {
+ enableSplitShade()
+ transitionController.goToLockedShade(null, needsQSAnimation = true)
+ verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
+ assertNotNull(transitionController.dragDownAnimator)
+ }
@Test
- fun testDragDownAmountCallsOut() {
- transitionController.dragDownAmount = 10f
- verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
- verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
- verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
- verify(transitionControllerCallback)
- .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
- verify(qsTransitionController).dragDownAmount = 10f
- verify(depthController).transitionToFullShadeProgress = anyFloat()
- }
+ fun testGoToLockedShadeCancelDoesntLeaveShadeOpenOnKeyguardHide() =
+ testScope.runTest {
+ whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
+ whenever(lockScreenUserManager.isLockscreenPublicMode(any())).thenReturn(true)
+ transitionController.goToLockedShade(null)
+ val captor = argumentCaptor<Runnable>()
+ verify(centralSurfaces)
+ .showBouncerWithDimissAndCancelIfKeyguard(isNull(), captor.capture())
+ captor.value.run()
+ verify(statusbarStateController).setLeaveOpenOnKeyguardHide(false)
+ }
@Test
- fun testDragDownAmount_depthDistanceIsZero_setsProgressToZero() {
- context
- .getOrCreateTestableResources()
- .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 0)
- configurationController.notifyConfigurationChanged()
-
- transitionController.dragDownAmount = 10f
-
- verify(depthController).transitionToFullShadeProgress = 0f
- }
+ fun testDragDownAmountDoesntCallOutInLockedDownShade() =
+ testScope.runTest {
+ whenever(nsslController.isInLockedDownShade).thenReturn(true)
+ transitionController.dragDownAmount = 10f
+ verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
+ verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
+ verify(scrimController, never())
+ .setTransitionToFullShadeProgress(anyFloat(), anyFloat())
+ verify(transitionControllerCallback, never())
+ .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
+ verify(qsTransitionController, never()).dragDownAmount = anyFloat()
+ }
@Test
- fun testDragDownAmount_depthDistanceNonZero_setsProgressBasedOnDistance() {
- context
- .getOrCreateTestableResources()
- .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 100)
- configurationController.notifyConfigurationChanged()
-
- transitionController.dragDownAmount = 10f
-
- verify(depthController).transitionToFullShadeProgress = 0.1f
- }
+ fun testDragDownAmountCallsOut() =
+ testScope.runTest {
+ transitionController.dragDownAmount = 10f
+ verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
+ verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
+ verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
+ verify(transitionControllerCallback)
+ .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
+ verify(qsTransitionController).dragDownAmount = 10f
+ verify(depthController).transitionToFullShadeProgress = anyFloat()
+ }
@Test
- fun setDragAmount_setsKeyguardTransitionProgress() {
- transitionController.dragDownAmount = 10f
+ fun testDragDownAmount_depthDistanceIsZero_setsProgressToZero() =
+ testScope.runTest {
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 0)
+ configurationController.notifyConfigurationChanged()
- verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), anyInt())
- }
+ transitionController.dragDownAmount = 10f
+
+ verify(depthController).transitionToFullShadeProgress = 0f
+ }
@Test
- fun setDragAmount_setsKeyguardAlphaBasedOnDistance() {
- val alphaDistance =
- context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
- )
- transitionController.dragDownAmount = 10f
+ fun testDragDownAmount_depthDistanceNonZero_setsProgressBasedOnDistance() =
+ testScope.runTest {
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 100)
+ configurationController.notifyConfigurationChanged()
- val expectedAlpha = 1 - 10f / alphaDistance
- verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(eq(expectedAlpha), anyInt())
- }
+ transitionController.dragDownAmount = 10f
+
+ verify(depthController).transitionToFullShadeProgress = 0.1f
+ }
@Test
- fun setDragAmount_notInSplitShade_setsKeyguardTranslationToZero() {
- val mediaTranslationY = 123
- disableSplitShade()
- whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
- whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
- .thenReturn(mediaTranslationY)
+ fun setDragAmount_setsKeyguardTransitionProgress() =
+ testScope.runTest {
+ transitionController.dragDownAmount = 10f
- transitionController.dragDownAmount = 10f
-
- verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), eq(0))
- }
+ verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), anyInt())
+ }
@Test
- fun setDragAmount_inSplitShade_setsKeyguardTranslationBasedOnMediaTranslation() {
- val mediaTranslationY = 123
- enableSplitShade()
- whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
- whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
- .thenReturn(mediaTranslationY)
+ fun setDragAmount_setsKeyguardAlphaBasedOnDistance() =
+ testScope.runTest {
+ val alphaDistance =
+ context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
+ )
+ transitionController.dragDownAmount = 10f
- transitionController.dragDownAmount = 10f
+ val expectedAlpha = 1 - 10f / alphaDistance
+ verify(shadeLockscreenInteractor)
+ .setKeyguardTransitionProgress(eq(expectedAlpha), anyInt())
+ }
- verify(shadeLockscreenInteractor)
+ @Test
+ fun setDragAmount_notInSplitShade_setsKeyguardTranslationToZero() =
+ testScope.runTest {
+ val mediaTranslationY = 123
+ disableSplitShade()
+ whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+ .thenReturn(mediaTranslationY)
+
+ transitionController.dragDownAmount = 10f
+
+ verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), eq(0))
+ }
+
+ @Test
+ fun setDragAmount_inSplitShade_setsKeyguardTranslationBasedOnMediaTranslation() =
+ testScope.runTest {
+ val mediaTranslationY = 123
+ enableSplitShade()
+ whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+ .thenReturn(mediaTranslationY)
+
+ transitionController.dragDownAmount = 10f
+
+ verify(shadeLockscreenInteractor)
.setKeyguardTransitionProgress(anyFloat(), eq(mediaTranslationY))
- }
+ }
@Test
- fun setDragAmount_inSplitShade_mediaNotShowing_setsKeyguardTranslationBasedOnDistance() {
- enableSplitShade()
- whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(false)
- whenever(mediaHierarchyManager.getGuidedTransformationTranslationY()).thenReturn(123)
+ fun setDragAmount_inSplitShade_mediaNotShowing_setsKeyguardTranslationBasedOnDistance() =
+ testScope.runTest {
+ enableSplitShade()
+ whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(false)
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY()).thenReturn(123)
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- val distance =
- context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_keyguard_transition_distance
+ val distance =
+ context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_keyguard_transition_distance
+ )
+ val offset =
+ context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_keyguard_transition_vertical_offset
+ )
+ val expectedTranslation = 10f / distance * offset
+ verify(shadeLockscreenInteractor)
+ .setKeyguardTransitionProgress(anyFloat(), eq(expectedTranslation.toInt()))
+ }
+
+ @Test
+ fun setDragDownAmount_setsValueOnMediaHierarchyManager() =
+ testScope.runTest {
+ transitionController.dragDownAmount = 10f
+
+ verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
+ }
+
+ @Test
+ fun setDragAmount_setsScrimProgressBasedOnScrimDistance() =
+ testScope.runTest {
+ val distance = 10
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_scrim_transition_distance,
+ distance
)
- val offset =
- context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_keyguard_transition_vertical_offset
+ configurationController.notifyConfigurationChanged()
+
+ transitionController.dragDownAmount = 5f
+
+ verify(scrimController)
+ .transitionToFullShadeProgress(
+ progress = eq(0.5f),
+ lockScreenNotificationsProgress = anyFloat()
+ )
+ }
+
+ @Test
+ fun setDragAmount_setsNotificationsScrimProgressBasedOnNotificationsScrimDistanceAndDelay() =
+ testScope.runTest {
+ val distance = 100
+ val delay = 10
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
+ distance
)
- val expectedTranslation = 10f / distance * offset
- verify(shadeLockscreenInteractor)
- .setKeyguardTransitionProgress(anyFloat(), eq(expectedTranslation.toInt()))
- }
-
- @Test
- fun setDragDownAmount_setsValueOnMediaHierarchyManager() {
- transitionController.dragDownAmount = 10f
-
- verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
- }
-
- @Test
- fun setDragAmount_setsScrimProgressBasedOnScrimDistance() {
- val distance = 10
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_scrim_transition_distance,
- distance
- )
- configurationController.notifyConfigurationChanged()
-
- transitionController.dragDownAmount = 5f
-
- verify(scrimController)
- .transitionToFullShadeProgress(
- progress = eq(0.5f),
- lockScreenNotificationsProgress = anyFloat()
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
+ delay
)
- }
+ configurationController.notifyConfigurationChanged()
+
+ transitionController.dragDownAmount = 20f
+
+ verify(scrimController)
+ .transitionToFullShadeProgress(
+ progress = anyFloat(),
+ lockScreenNotificationsProgress = eq(0.1f)
+ )
+ }
@Test
- fun setDragAmount_setsNotificationsScrimProgressBasedOnNotificationsScrimDistanceAndDelay() {
- val distance = 100
- val delay = 10
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
- distance
- )
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
- delay
- )
- configurationController.notifyConfigurationChanged()
-
- transitionController.dragDownAmount = 20f
-
- verify(scrimController)
- .transitionToFullShadeProgress(
- progress = anyFloat(),
- lockScreenNotificationsProgress = eq(0.1f)
+ fun setDragAmount_dragAmountLessThanNotifDelayDistance_setsNotificationsScrimProgressToZero() =
+ testScope.runTest {
+ val distance = 100
+ val delay = 50
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
+ distance
)
- }
-
- @Test
- fun setDragAmount_dragAmountLessThanNotifDelayDistance_setsNotificationsScrimProgressToZero() {
- val distance = 100
- val delay = 50
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
- distance
- )
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
- delay
- )
- configurationController.notifyConfigurationChanged()
-
- transitionController.dragDownAmount = 20f
-
- verify(scrimController)
- .transitionToFullShadeProgress(
- progress = anyFloat(),
- lockScreenNotificationsProgress = eq(0f)
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
+ delay
)
- }
+ configurationController.notifyConfigurationChanged()
+
+ transitionController.dragDownAmount = 20f
+
+ verify(scrimController)
+ .transitionToFullShadeProgress(
+ progress = anyFloat(),
+ lockScreenNotificationsProgress = eq(0f)
+ )
+ }
@Test
- fun setDragAmount_dragAmountMoreThanTotalDistance_setsNotificationsScrimProgressToOne() {
- val distance = 100
- val delay = 50
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
- distance
- )
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
- delay
- )
- configurationController.notifyConfigurationChanged()
-
- transitionController.dragDownAmount = 999999f
-
- verify(scrimController)
- .transitionToFullShadeProgress(
- progress = anyFloat(),
- lockScreenNotificationsProgress = eq(1f)
+ fun setDragAmount_dragAmountMoreThanTotalDistance_setsNotificationsScrimProgressToOne() =
+ testScope.runTest {
+ val distance = 100
+ val delay = 50
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
+ distance
)
- }
-
- @Test
- fun setDragDownAmount_inSplitShade_setsValueOnMediaHierarchyManager() {
- enableSplitShade()
-
- transitionController.dragDownAmount = 10f
-
- verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
- }
-
- @Test
- fun setDragAmount_notInSplitShade_forwardsToSingleShadeOverScroller() {
- disableSplitShade()
-
- transitionController.dragDownAmount = 10f
-
- verify(singleShadeOverScroller).expansionDragDownAmount = 10f
- verifyZeroInteractions(splitShadeOverScroller)
- }
-
- @Test
- fun setDragAmount_inSplitShade_forwardsToSplitShadeOverScroller() {
- enableSplitShade()
-
- transitionController.dragDownAmount = 10f
-
- verify(splitShadeOverScroller).expansionDragDownAmount = 10f
- verifyZeroInteractions(singleShadeOverScroller)
- }
-
- @Test
- fun setDragDownAmount_inSplitShade_setsKeyguardStatusBarAlphaBasedOnDistance() {
- val alphaDistance =
- context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
+ delay
)
- val dragDownAmount = 10f
- enableSplitShade()
+ configurationController.notifyConfigurationChanged()
- transitionController.dragDownAmount = dragDownAmount
+ transitionController.dragDownAmount = 999999f
- val expectedAlpha = 1 - dragDownAmount / alphaDistance
- verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(expectedAlpha)
- }
+ verify(scrimController)
+ .transitionToFullShadeProgress(
+ progress = anyFloat(),
+ lockScreenNotificationsProgress = eq(1f)
+ )
+ }
@Test
- fun setDragDownAmount_notInSplitShade_setsKeyguardStatusBarAlphaToMinusOne() {
- disableSplitShade()
+ fun setDragDownAmount_inSplitShade_setsValueOnMediaHierarchyManager() =
+ testScope.runTest {
+ enableSplitShade()
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(-1f)
- }
+ verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
+ }
@Test
- fun nullQs_canDragDownFromAdapter() {
- transitionController.qS = null
+ fun setDragAmount_notInSplitShade_forwardsToSingleShadeOverScroller() =
+ testScope.runTest {
+ disableSplitShade()
- qsSceneAdapter.isQsFullyCollapsed = true
- assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
- qsSceneAdapter.isQsFullyCollapsed = false
- assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
- }
+ transitionController.dragDownAmount = 10f
+
+ verify(singleShadeOverScroller).expansionDragDownAmount = 10f
+ verifyZeroInteractions(splitShadeOverScroller)
+ }
+
+ @Test
+ fun setDragAmount_inSplitShade_forwardsToSplitShadeOverScroller() =
+ testScope.runTest {
+ enableSplitShade()
+
+ transitionController.dragDownAmount = 10f
+
+ verify(splitShadeOverScroller).expansionDragDownAmount = 10f
+ verifyZeroInteractions(singleShadeOverScroller)
+ }
+
+ @Test
+ fun setDragDownAmount_inSplitShade_setsKeyguardStatusBarAlphaBasedOnDistance() =
+ testScope.runTest {
+ val alphaDistance =
+ context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
+ )
+ val dragDownAmount = 10f
+ enableSplitShade()
+
+ transitionController.dragDownAmount = dragDownAmount
+
+ val expectedAlpha = 1 - dragDownAmount / alphaDistance
+ verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(expectedAlpha)
+ }
+
+ @Test
+ fun setDragDownAmount_notInSplitShade_setsKeyguardStatusBarAlphaToMinusOne() =
+ testScope.runTest {
+ disableSplitShade()
+
+ transitionController.dragDownAmount = 10f
+
+ verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(-1f)
+ }
+
+ @Test
+ fun nullQs_canDragDownFromAdapter() =
+ testScope.runTest {
+ transitionController.qS = null
+
+ qsSceneAdapter.isQsFullyCollapsed = true
+ assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
+ qsSceneAdapter.isQsFullyCollapsed = false
+ assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
+ }
private fun enableSplitShade() {
setSplitShadeEnabled(true)
@@ -619,32 +631,4 @@
) {
setTransitionToFullShadeProgress(progress, lockScreenNotificationsProgress)
}
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- UserDomainLayerModule::class,
- BiometricsDomainLayerModule::class,
- ]
- )
- interface TestComponent {
-
- val configurationController: FakeConfigurationController
- val disableFlagsRepository: FakeDisableFlagsRepository
- val powerInteractor: PowerInteractor
- val shadeInteractor: ShadeInteractor
- val shadeRepository: FakeShadeRepository
- val testScope: TestScope
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
index 8a6a50d..ecb1a6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
@@ -70,7 +70,7 @@
}
@Test
- fun mediaRouterCastingState_connectingDevice_casting() =
+ fun mediaRouterCastingState_connectingDevice_casting_withName() =
testScope.runTest {
val latest by collectLastValue(underTest.mediaRouterCastingState)
@@ -79,17 +79,18 @@
CastDevice(
state = CastDevice.CastState.Connecting,
id = "id",
- name = "name",
+ name = "My Favorite Device",
description = "desc",
origin = CastDevice.CastOrigin.MediaRouter,
)
)
- assertThat(latest).isEqualTo(MediaRouterCastModel.Casting)
+ assertThat(latest)
+ .isEqualTo(MediaRouterCastModel.Casting(deviceName = "My Favorite Device"))
}
@Test
- fun mediaRouterCastingState_connectedDevice_casting() =
+ fun mediaRouterCastingState_connectedDevice_casting_withName() =
testScope.runTest {
val latest by collectLastValue(underTest.mediaRouterCastingState)
@@ -98,13 +99,14 @@
CastDevice(
state = CastDevice.CastState.Connected,
id = "id",
- name = "name",
+ name = "My Second Favorite Device",
description = "desc",
origin = CastDevice.CastOrigin.MediaRouter,
)
)
- assertThat(latest).isEqualTo(MediaRouterCastModel.Casting)
+ assertThat(latest)
+ .isEqualTo(MediaRouterCastModel.Casting(deviceName = "My Second Favorite Device"))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
index e9d6f0e..c8397bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
@@ -19,8 +19,10 @@
import android.content.ComponentName
import android.content.DialogInterface
import android.content.Intent
+import android.content.applicationContext
import android.content.packageManager
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
@@ -68,21 +70,87 @@
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setTitle(R.string.cast_screen_to_other_device_stop_dialog_title)
+ verify(sysuiDialog).setTitle(R.string.cast_to_other_device_stop_dialog_title)
}
@Test
- fun message_entireScreen() {
- createAndSetDelegate(ENTIRE_SCREEN)
+ fun message_entireScreen_unknownDevice() {
+ createAndSetDelegate(ENTIRE_SCREEN, deviceName = null)
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
verify(sysuiDialog)
- .setMessage(context.getString(R.string.cast_screen_to_other_device_stop_dialog_message))
+ .setMessage(
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_entire_screen)
+ )
}
@Test
- fun message_singleTask() {
+ fun message_entireScreen_hasDevice() {
+ createAndSetDelegate(ENTIRE_SCREEN, deviceName = "My Favorite Device")
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_entire_screen_with_device,
+ "My Favorite Device",
+ )
+ )
+ }
+
+ @Test
+ fun message_singleTask_unknownAppName_unknownDevice() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ ),
+ deviceName = null,
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
+ )
+ }
+
+ @Test
+ fun message_singleTask_unknownAppName_hasDevice() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ ),
+ deviceName = "My Favorite Device",
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
+ "My Favorite Device",
+ )
+ )
+ }
+
+ @Test
+ fun message_singleTask_hasAppName_unknownDevice() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
val appInfo = mock<ApplicationInfo>()
@@ -94,16 +162,48 @@
MediaProjectionState.Projecting.SingleTask(
HOST_PACKAGE,
createTask(taskId = 1, baseIntent = baseIntent)
- )
+ ),
+ deviceName = null,
)
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- // It'd be nice to use R.string.cast_screen_to_other_device_stop_dialog_message_specific_app
- // directly, but it includes the <b> tags which aren't in the returned string.
- val result = argumentCaptor<CharSequence>()
- verify(sysuiDialog).setMessage(result.capture())
- assertThat(result.firstValue.toString()).isEqualTo("You will stop casting Fake Package")
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_specific_app,
+ "Fake Package",
+ )
+ )
+ }
+
+ @Test
+ fun message_singleTask_hasAppName_hasDevice() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ val appInfo = mock<ApplicationInfo>()
+ whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenReturn(appInfo)
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ ),
+ deviceName = "My Favorite Device",
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_specific_app_with_device,
+ "Fake Package",
+ "My Favorite Device",
+ )
+ )
}
@Test
@@ -140,14 +240,19 @@
assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
}
- private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
+ private fun createAndSetDelegate(
+ state: MediaProjectionState.Projecting,
+ deviceName: String? = null,
+ ) {
underTest =
EndCastScreenToOtherDeviceDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
+ kosmos.applicationContext,
stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
ProjectionChipModel.Projecting(
ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE,
state,
+ deviceName,
),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
index 0af423d..e6101f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
import android.content.DialogInterface
+import android.content.applicationContext
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -65,12 +66,30 @@
}
@Test
- fun message() {
- createAndSetDelegate()
+ fun message_unknownDevice() {
+ createAndSetDelegate(deviceName = null)
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setMessage(R.string.cast_to_other_device_stop_dialog_message)
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
+ )
+ }
+
+ @Test
+ fun message_hasDevice() {
+ createAndSetDelegate(deviceName = "My Favorite Device")
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
+ "My Favorite Device",
+ )
+ )
}
@Test
@@ -122,10 +141,12 @@
assertThat(kosmos.fakeMediaRouterRepository.lastStoppedDevice).isEqualTo(device)
}
- private fun createAndSetDelegate() {
+ private fun createAndSetDelegate(deviceName: String? = null) {
underTest =
EndGenericCastToOtherDeviceDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
+ kosmos.applicationContext,
+ deviceName,
stopAction = kosmos.mediaRouterChipInteractor::stopCasting,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
index f9ad5ac..ab935fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
@@ -27,7 +27,6 @@
import com.android.systemui.kosmos.testCase
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.google.common.truth.Truth.assertThat
@@ -56,19 +55,15 @@
}
@Test
- fun getDialogMessage_entireScreen_isGenericMessage() {
+ fun getAppName_stateVersion_entireScreen_returnsNull() {
val result =
- underTest.getDialogMessage(
- MediaProjectionState.Projecting.EntireScreen("host.package"),
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ underTest.getAppName(MediaProjectionState.Projecting.EntireScreen("host.package"))
- assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ assertThat(result).isNull()
}
@Test
- fun getDialogMessage_singleTask_cannotFindPackage_isGenericMessage() {
+ fun getAppName_stateVersion_singleTask_cannotFindPackage_returnsNull() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
@@ -77,21 +72,16 @@
val projectionState =
MediaProjectionState.Projecting.SingleTask(
"host.package",
- createTask(taskId = 1, baseIntent = baseIntent)
+ createTask(taskId = 1, baseIntent = baseIntent),
)
- val result =
- underTest.getDialogMessage(
- projectionState,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ val result = underTest.getAppName(projectionState)
- assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ assertThat(result).isNull()
}
@Test
- fun getDialogMessage_singleTask_findsPackage_isSpecificMessageWithAppLabel() {
+ fun getAppName_stateVersion_singleTask_findsPackage_returnsName() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
val appInfo = mock<ApplicationInfo>()
@@ -102,93 +92,66 @@
val projectionState =
MediaProjectionState.Projecting.SingleTask(
"host.package",
- createTask(taskId = 1, baseIntent = baseIntent)
+ createTask(taskId = 1, baseIntent = baseIntent),
)
- val result =
- underTest.getDialogMessage(
- projectionState,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ val result = underTest.getAppName(projectionState)
- // It'd be nice to use the R.string resources directly, but they include the <b> tags which
- // aren't in the returned string.
- assertThat(result.toString()).isEqualTo("You will stop casting Fake Package")
+ assertThat(result).isEqualTo("Fake Package")
}
@Test
- fun getDialogMessage_nullTask_isGenericMessage() {
- val result =
- underTest.getDialogMessage(
- specificTaskInfo = null,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ fun getAppName_taskInfoVersion_null_returnsNull() {
+ val result = underTest.getAppName(specificTaskInfo = null)
- assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ assertThat(result).isNull()
}
@Test
- fun getDialogMessage_withTask_cannotFindPackage_isGenericMessage() {
+ fun getAppName_taskInfoVersion_cannotFindPackage_returnsNull() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
.thenThrow(PackageManager.NameNotFoundException())
- val task = createTask(taskId = 1, baseIntent = baseIntent)
- val result =
- underTest.getDialogMessage(
- task,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ val result = underTest.getAppName(createTask(taskId = 1, baseIntent = baseIntent))
- assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ assertThat(result).isNull()
}
@Test
- fun getDialogMessage_withTask_findsPackage_isSpecificMessageWithAppLabel() {
+ fun getAppName_taskInfoVersion_findsPackage_returnsName() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
val appInfo = mock<ApplicationInfo>()
whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
.thenReturn(appInfo)
- val task = createTask(taskId = 1, baseIntent = baseIntent)
- val result =
- underTest.getDialogMessage(
- task,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ val result = underTest.getAppName(createTask(taskId = 1, baseIntent = baseIntent))
- assertThat(result.toString()).isEqualTo("You will stop casting Fake Package")
+ assertThat(result).isEqualTo("Fake Package")
}
@Test
- fun getDialogMessage_appLabelHasSpecialCharacters_isEscaped() {
- val baseIntent =
- Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ fun getAppName_packageNameVersion_cannotFindPackage_returnsNull() {
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ val result = underTest.getAppName("fake.task.package")
+
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun getAppName_packageNameVersion_findsPackage_returnsName() {
val appInfo = mock<ApplicationInfo>()
- whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake & Package <Here>")
+ whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
.thenReturn(appInfo)
- val projectionState =
- MediaProjectionState.Projecting.SingleTask(
- "host.package",
- createTask(taskId = 1, baseIntent = baseIntent)
- )
+ val result = underTest.getAppName("fake.task.package")
- val result =
- underTest.getDialogMessage(
- projectionState,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
-
- assertThat(result.toString()).isEqualTo("You will stop casting Fake & Package <Here>")
+ assertThat(result).isEqualTo("Fake Package")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
index 7e667de..bfb63ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
@@ -20,6 +20,7 @@
import android.content.ComponentName
import android.content.DialogInterface
import android.content.Intent
+import android.content.applicationContext
import android.content.packageManager
import android.content.pm.ApplicationInfo
import androidx.test.filters.SmallTest
@@ -95,11 +96,13 @@
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- // It'd be nice to use R.string.screenrecord_stop_dialog_message_specific_app directly, but
- // it includes the <b> tags which aren't in the returned string.
- val result = argumentCaptor<CharSequence>()
- verify(sysuiDialog).setMessage(result.capture())
- assertThat(result.firstValue.toString()).isEqualTo("You will stop recording Fake Package")
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.screenrecord_stop_dialog_message_specific_app,
+ "Fake Package",
+ )
+ )
}
@Test
@@ -140,6 +143,7 @@
underTest =
EndScreenRecordingDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
+ kosmos.applicationContext,
stopAction = kosmos.screenRecordChipInteractor::stopRecording,
recordedTask,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
index 63c29ac..bfb57c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
@@ -19,8 +19,10 @@
import android.content.ComponentName
import android.content.DialogInterface
import android.content.Intent
+import android.content.applicationContext
import android.content.packageManager
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
@@ -65,6 +67,8 @@
@Test
fun title() {
createAndSetDelegate(ENTIRE_SCREEN)
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -72,16 +76,60 @@
}
@Test
- fun message_entireScreen() {
+ fun message_entireScreen_unknownHostPackage() {
createAndSetDelegate(ENTIRE_SCREEN)
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setMessage(context.getString(R.string.share_to_app_stop_dialog_message))
+ verify(sysuiDialog)
+ .setMessage(context.getString(R.string.share_to_app_stop_dialog_message_entire_screen))
}
@Test
- fun message_singleTask() {
+ fun message_entireScreen_hasHostPackage() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+ val hostAppInfo = mock<ApplicationInfo>()
+ whenever(hostAppInfo.loadLabel(kosmos.packageManager)).thenReturn("Host Package")
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenReturn(hostAppInfo)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.share_to_app_stop_dialog_message_entire_screen_with_host_app,
+ "Host Package",
+ )
+ )
+ }
+
+ @Test
+ fun message_singleTask_unknownAppName() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ )
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(R.string.share_to_app_stop_dialog_message_single_app_generic)
+ )
+ }
+
+ @Test
+ fun message_singleTask_hasAppName() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
val appInfo = mock<ApplicationInfo>()
@@ -98,11 +146,13 @@
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- // It'd be nice to use R.string.share_to_app_stop_dialog_message_specific_app directly, but
- // it includes the <b> tags which aren't in the returned string.
- val result = argumentCaptor<CharSequence>()
- verify(sysuiDialog).setMessage(result.capture())
- assertThat(result.firstValue.toString()).isEqualTo("You will stop sharing Fake Package")
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.share_to_app_stop_dialog_message_single_app_specific,
+ "Fake Package",
+ )
+ )
}
@Test
@@ -118,6 +168,8 @@
fun positiveButton() =
kosmos.testScope.runTest {
createAndSetDelegate(ENTIRE_SCREEN)
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -143,8 +195,13 @@
underTest =
EndShareToAppDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
+ kosmos.applicationContext,
stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
- ProjectionChipModel.Projecting(ProjectionChipModel.Type.SHARE_TO_APP, state),
+ ProjectionChipModel.Projecting(
+ ProjectionChipModel.Type.SHARE_TO_APP,
+ state,
+ deviceName = null,
+ ),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 26f5370..f07303e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -17,21 +17,19 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.shared.byIsAmbient
import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
import com.android.systemui.statusbar.notification.shared.byIsPulsing
@@ -39,15 +37,15 @@
import com.android.systemui.statusbar.notification.shared.byIsSilent
import com.android.systemui.statusbar.notification.shared.byIsSuppressedFromStatusBar
import com.android.systemui.statusbar.notification.shared.byKey
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.bubbles.Bubbles
+import com.android.wm.shell.bubbles.bubbles
+import com.android.wm.shell.bubbles.bubblesOptional
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,29 +53,22 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class NotificationIconsInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
- private val bubbles: Bubbles = mock()
-
- @Component(modules = [SysUITestModule::class])
- @SysUISingleton
- interface TestComponent : SysUITestComponent<NotificationIconsInteractor> {
-
- val activeNotificationListRepository: ActiveNotificationListRepository
- val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
-
- @Component.Factory
- interface Factory {
- fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
- }
- }
-
- val testComponent: TestComponent =
- DaggerNotificationIconsInteractorTest_TestComponent.factory()
- .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+ private val underTest =
+ NotificationIconsInteractor(
+ kosmos.activeNotificationsInteractor,
+ kosmos.bubblesOptional,
+ kosmos.headsUpNotificationIconInteractor,
+ kosmos.notificationsKeyguardViewStateRepository
+ )
@Before
fun setup() {
- testComponent.apply {
+ testScope.apply {
activeNotificationListRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
.apply { testIcons.forEach(::addIndividualNotif) }
@@ -87,22 +78,22 @@
@Test
fun filteredEntrySet() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet())
assertThat(filteredSet).containsExactlyElementsIn(testIcons)
}
@Test
fun filteredEntrySet_noExpandedBubbles() =
- testComponent.runTest {
- whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ testScope.runTest {
+ whenever(kosmos.bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
val filteredSet by collectLastValue(underTest.filteredNotifSet())
assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
}
@Test
fun filteredEntrySet_noAmbient() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showAmbient = false))
assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
assertThat(filteredSet)
@@ -112,21 +103,21 @@
@Test
fun filteredEntrySet_noLowPriority() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showLowPriority = false))
assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
}
@Test
fun filteredEntrySet_noDismissed() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showDismissed = false))
assertThat(filteredSet).comparingElementsUsing(byIsRowDismissed).doesNotContain(true)
}
@Test
fun filteredEntrySet_noRepliedMessages() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by
collectLastValue(underTest.filteredNotifSet(showRepliedMessages = false))
assertThat(filteredSet)
@@ -136,7 +127,7 @@
@Test
fun filteredEntrySet_noPulsing_notifsNotFullyHidden() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
@@ -144,65 +135,46 @@
@Test
fun filteredEntrySet_noPulsing_notifsFullyHidden() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
}
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
- private val bubbles: Bubbles = mock()
-
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- @SysUISingleton
- interface TestComponent : SysUITestComponent<AlwaysOnDisplayNotificationIconsInteractor> {
-
- val activeNotificationListRepository: ActiveNotificationListRepository
- val deviceEntryRepository: FakeDeviceEntryRepository
- val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
-
- @Component.Factory
- interface Factory {
- fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
- }
- }
-
- private val testComponent: TestComponent =
- DaggerAlwaysOnDisplayNotificationIconsInteractorTest_TestComponent.factory()
- .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+ private val underTest =
+ AlwaysOnDisplayNotificationIconsInteractor(
+ kosmos.testDispatcher,
+ kosmos.deviceEntryInteractor,
+ kosmos.notificationIconsInteractor,
+ )
@Before
fun setup() {
- testComponent.apply {
- activeNotificationListRepository.activeNotifications.value =
- ActiveNotificationsStore.Builder()
- .apply { testIcons.forEach(::addIndividualNotif) }
- .build()
- }
+ kosmos.activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { testIcons.forEach(::addIndividualNotif) }
+ .build()
}
@Test
fun filteredEntrySet_noExpandedBubbles() =
- testComponent.runTest {
- whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ testScope.runTest {
+ whenever(kosmos.bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
val filteredSet by collectLastValue(underTest.aodNotifs)
assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
}
@Test
fun filteredEntrySet_noAmbient() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
assertThat(filteredSet)
@@ -212,14 +184,14 @@
@Test
fun filteredEntrySet_noDismissed() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
assertThat(filteredSet).comparingElementsUsing(byIsRowDismissed).doesNotContain(true)
}
@Test
fun filteredEntrySet_noRepliedMessages() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
assertThat(filteredSet)
.comparingElementsUsing(byIsLastMessageFromReply)
@@ -228,37 +200,37 @@
@Test
fun filteredEntrySet_showPulsing_notifsNotFullyHidden_bypassDisabled() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
- deviceEntryRepository.setBypassEnabled(false)
- notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
+ kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
@Test
fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassDisabled() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
- deviceEntryRepository.setBypassEnabled(false)
- notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
+ kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
@Test
fun filteredEntrySet_noPulsing_notifsNotFullyHidden_bypassEnabled() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
- deviceEntryRepository.setBypassEnabled(true)
- notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
+ kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
}
@Test
fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassEnabled() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
- deviceEntryRepository.setBypassEnabled(true)
- notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
+ kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
}
@@ -266,32 +238,19 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
-
- private val bubbles: Bubbles = mock()
-
- @Component(modules = [SysUITestModule::class])
- @SysUISingleton
- interface TestComponent : SysUITestComponent<StatusBarNotificationIconsInteractor> {
-
- val activeNotificationListRepository: ActiveNotificationListRepository
- val headsUpIconsInteractor: HeadsUpNotificationIconInteractor
- val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
- val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
-
- @Component.Factory
- interface Factory {
- fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
- }
- }
-
- val testComponent: TestComponent =
- DaggerStatusBarNotificationIconsInteractorTest_TestComponent.factory()
- .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest =
+ StatusBarNotificationIconsInteractor(
+ kosmos.testDispatcher,
+ kosmos.notificationIconsInteractor,
+ kosmos.notificationListenerSettingsRepository,
+ )
@Before
fun setup() {
- testComponent.apply {
- activeNotificationListRepository.activeNotifications.value =
+ testScope.apply {
+ kosmos.activeNotificationListRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
.apply { testIcons.forEach(::addIndividualNotif) }
.build()
@@ -300,15 +259,15 @@
@Test
fun filteredEntrySet_noExpandedBubbles() =
- testComponent.runTest {
- whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ testScope.runTest {
+ whenever(kosmos.bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
val filteredSet by collectLastValue(underTest.statusBarNotifs)
assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
}
@Test
fun filteredEntrySet_noAmbient() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
assertThat(filteredSet)
@@ -318,30 +277,30 @@
@Test
fun filteredEntrySet_noLowPriority_whenDontShowSilentIcons() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
- notificationListenerSettingsRepository.showSilentStatusIcons.value = false
+ kosmos.notificationListenerSettingsRepository.showSilentStatusIcons.value = false
assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
}
@Test
fun filteredEntrySet_showLowPriority_whenShowSilentIcons() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
- notificationListenerSettingsRepository.showSilentStatusIcons.value = true
+ kosmos.notificationListenerSettingsRepository.showSilentStatusIcons.value = true
assertThat(filteredSet).comparingElementsUsing(byIsSilent).contains(true)
}
@Test
fun filteredEntrySet_noDismissed() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
assertThat(filteredSet).comparingElementsUsing(byIsRowDismissed).doesNotContain(true)
}
@Test
fun filteredEntrySet_noRepliedMessages() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
assertThat(filteredSet)
.comparingElementsUsing(byIsLastMessageFromReply)
@@ -350,9 +309,9 @@
@Test
fun filteredEntrySet_includesIsolatedIcon() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
- headsUpIconsInteractor.setIsolatedIconNotificationKey("notif5")
+ kosmos.headsUpNotificationIconInteractor.setIsolatedIconNotificationKey("notif5")
assertThat(filteredSet).comparingElementsUsing(byKey).contains("notif5")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 894e02e..1f4e80e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -16,111 +16,81 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.content.res.mainResources
import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.alwaysOnDisplayNotificationIconsInteractor
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent :
- SysUITestComponent<NotificationIconContainerAlwaysOnDisplayViewModel> {
-
- val deviceProvisioningRepository: FakeDeviceProvisioningRepository
- val keyguardRepository: FakeKeyguardRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val powerRepository: FakePowerRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- featureFlags: FakeFeatureFlagsClassicModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, value = false) }
}
- }
- private val dozeParams: DozeParameters = mock()
- private val screenOffAnimController: ScreenOffAnimationController = mock()
-
- private val testComponent: TestComponent =
- DaggerNotificationIconContainerAlwaysOnDisplayViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParams,
- screenOffAnimationController = screenOffAnimController,
- ),
- )
+ val underTest =
+ NotificationIconContainerAlwaysOnDisplayViewModel(
+ kosmos.testDispatcher,
+ kosmos.alwaysOnDisplayNotificationIconsInteractor,
+ kosmos.keyguardInteractor,
+ kosmos.keyguardTransitionInteractor,
+ kosmos.mainResources,
+ kosmos.shadeInteractor,
+ )
+ val testScope = kosmos.testScope
+ val keyguardRepository = kosmos.fakeKeyguardRepository
+ val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val powerRepository = kosmos.fakePowerRepository
@Before
fun setup() {
- testComponent.apply {
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.AWAKE,
- lastWakeReason = WakeSleepReason.OTHER,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- }
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ kosmos.fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
}
@Test
fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.ASLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -143,7 +113,7 @@
@Test
fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.ASLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -166,7 +136,7 @@
@Test
fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_SLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -179,7 +149,7 @@
transitionState = TransitionState.STARTED,
)
)
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
+ whenever(kosmos.dozeParameters.shouldControlScreenOff()).thenReturn(false)
val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
runCurrent()
assertThat(animationsEnabled).isFalse()
@@ -187,7 +157,7 @@
@Test
fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
- testComponent.runTest {
+ testScope.runTest {
val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
assertThat(animationsEnabled).isTrue()
@@ -203,13 +173,13 @@
transitionState = TransitionState.STARTED,
)
)
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
+ whenever(kosmos.dozeParameters.shouldControlScreenOff()).thenReturn(true)
assertThat(animationsEnabled).isTrue()
}
@Test
fun animationsEnabled_isTrue_whenNotAsleep() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.AWAKE,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -228,7 +198,7 @@
@Test
@DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun animationsEnabled_isTrue_whenKeyguardIsShowing() =
- testComponent.runTest {
+ testScope.runTest {
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
@@ -257,7 +227,7 @@
@Test
fun tintAlpha_isZero_whenNotOnAodOrDozing() =
- testComponent.runTest {
+ testScope.runTest {
val tintAlpha by collectLastValue(underTest.tintAlpha)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
@@ -271,7 +241,7 @@
@Test
fun tintAlpha_isOne_whenOnAod() =
- testComponent.runTest {
+ testScope.runTest {
val tintAlpha by collectLastValue(underTest.tintAlpha)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
@@ -285,7 +255,7 @@
@Test
fun tintAlpha_isOne_whenDozing() =
- testComponent.runTest {
+ testScope.runTest {
val tintAlpha by collectLastValue(underTest.tintAlpha)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
@@ -298,7 +268,7 @@
@Test
fun tintAlpha_isOne_whenTransitionFromAodToDoze() =
- testComponent.runTest {
+ testScope.runTest {
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.AOD,
@@ -332,7 +302,7 @@
@Test
fun tintAlpha_isFraction_midTransitionToAod() =
- testComponent.runTest {
+ testScope.runTest {
val tintAlpha by collectLastValue(underTest.tintAlpha)
runCurrent()
@@ -361,7 +331,7 @@
@Test
fun iconAnimationsEnabled_whenOnLockScreen() =
- testComponent.runTest {
+ testScope.runTest {
val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
runCurrent()
@@ -376,7 +346,7 @@
@Test
fun iconAnimationsDisabled_whenOnAod() =
- testComponent.runTest {
+ testScope.runTest {
val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
runCurrent()
@@ -391,7 +361,7 @@
@Test
fun iconAnimationsDisabled_whenDozing() =
- testComponent.runTest {
+ testScope.runTest {
val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 3ca4c59..0e4d892 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -37,6 +37,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -66,6 +68,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -74,8 +77,11 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.ui.BouncerView;
import com.android.systemui.bouncer.ui.BouncerViewDelegate;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -158,6 +164,7 @@
@Mock private TaskbarDelegate mTaskbarDelegate;
@Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
+ @Mock private DeviceEntryInteractor mDeviceEntryInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -178,6 +185,7 @@
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Before
+ @DisableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
@@ -185,10 +193,6 @@
.thenReturn(mKeyguardMessageAreaController);
when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
- mSetFlagsRule.disableFlags(
- com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
- com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
- );
when(mNotificationShadeWindowController.getWindowRootView())
.thenReturn(mNotificationShadeWindowView);
@@ -227,7 +231,8 @@
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
() -> mock(SceneInteractor.class),
- mock(StatusBarKeyguardViewManagerInteractor.class)) {
+ mock(StatusBarKeyguardViewManagerInteractor.class),
+ () -> mDeviceEntryInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -250,6 +255,7 @@
}
@Test
+ @DisableSceneContainer
public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
OnDismissAction action = () -> false;
Runnable cancelAction = () -> {
@@ -265,6 +271,7 @@
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
+ verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
}
@Test
@@ -274,9 +281,11 @@
KeyguardSecurityModel.SecurityMode.Password);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
+ verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
}
@Test
+ @DisableSceneContainer
public void showBouncer_showsTheBouncer() {
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor).show(eq(true));
@@ -320,6 +329,7 @@
}
@Test
+ @DisableSceneContainer
public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
mKeyguardStateController.setCanDismissLockScreen(false);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
@@ -456,6 +466,7 @@
}
@Test
+ @DisableSceneContainer
public void testHiding_cancelsGoneRunnable() {
OnDismissAction action = mock(OnDismissAction.class);
Runnable cancelAction = mock(Runnable.class);
@@ -470,6 +481,7 @@
}
@Test
+ @DisableSceneContainer
public void testHidingBouncer_cancelsGoneRunnable() {
OnDismissAction action = mock(OnDismissAction.class);
Runnable cancelAction = mock(Runnable.class);
@@ -484,6 +496,7 @@
}
@Test
+ @DisableSceneContainer
public void testHiding_doesntCancelWhenShowing() {
OnDismissAction action = mock(OnDismissAction.class);
Runnable cancelAction = mock(Runnable.class);
@@ -539,6 +552,7 @@
}
@Test
+ @DisableSceneContainer
public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
// GIVEN cannot use alternate bouncer
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
@@ -553,6 +567,8 @@
}
@Test
+ @DisableSceneContainer
+ @DisableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
// GIVEN will show alternate bouncer
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
@@ -735,7 +751,8 @@
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
() -> mock(SceneInteractor.class),
- mock(StatusBarKeyguardViewManagerInteractor.class)) {
+ mock(StatusBarKeyguardViewManagerInteractor.class),
+ () -> mDeviceEntryInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -783,11 +800,11 @@
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void handleDispatchTouchEvent_alternateBouncerViewFlagEnabled() {
mStatusBarKeyguardViewManager.addCallback(mCallback);
// GIVEN alternate bouncer view flag enabled & the alternate bouncer is visible
- mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
// THEN the touch is not acted upon
@@ -795,9 +812,9 @@
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void onInterceptTouch_alternateBouncerViewFlagEnabled() {
// GIVEN alternate bouncer view flag enabled & the alternate bouncer is visible
- mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
// THEN the touch is not intercepted
@@ -829,6 +846,8 @@
}
@Test
+ @DisableSceneContainer
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void handleDispatchTouchEvent_shouldInterceptTouchAndHandleTouch() {
mStatusBarKeyguardViewManager.addCallback(mCallback);
@@ -855,6 +874,8 @@
}
@Test
+ @DisableSceneContainer
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void handleDispatchTouchEvent_shouldInterceptTouchButNotHandleTouch() {
mStatusBarKeyguardViewManager.addCallback(mCallback);
@@ -881,6 +902,7 @@
}
@Test
+ @DisableSceneContainer
public void shouldInterceptTouch_alternateBouncerNotVisible() {
// GIVEN the alternate bouncer is not visible
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
@@ -898,6 +920,8 @@
}
@Test
+ @DisableSceneContainer
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void shouldInterceptTouch_alternateBouncerVisible() {
// GIVEN the alternate bouncer is visible
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
@@ -931,6 +955,8 @@
}
@Test
+ @DisableSceneContainer
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void alternateBouncerOnTouch_actionDownThenUp_noMinTimeShown_noHideAltBouncer() {
reset(mAlternateBouncerInteractor);
@@ -955,6 +981,8 @@
}
@Test
+ @DisableSceneContainer
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void alternateBouncerOnTouch_actionDownThenUp_handlesTouch_hidesAltBouncer() {
reset(mAlternateBouncerInteractor);
@@ -979,6 +1007,7 @@
}
@Test
+ @DisableSceneContainer
public void alternateBouncerOnTouch_actionUp_doesNotHideAlternateBouncer() {
reset(mAlternateBouncerInteractor);
@@ -996,6 +1025,8 @@
}
@Test
+ @DisableSceneContainer
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void onTrustChanged_hideAlternateBouncerAndClearMessageArea() {
// GIVEN keyguard update monitor callback is registered
verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture());
@@ -1024,6 +1055,7 @@
}
@Test
+ @DisableSceneContainer
public void testShowBouncerOrKeyguard_needsFullScreen() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
@@ -1033,6 +1065,7 @@
}
@Test
+ @DisableSceneContainer
public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
@@ -1043,6 +1076,20 @@
}
@Test
+ @EnableSceneContainer
+ public void showBouncer_attemptDeviceEntry() {
+ mStatusBarKeyguardViewManager.showBouncer(false);
+ verify(mDeviceEntryInteractor).attemptDeviceEntry();
+ }
+
+ @Test
+ @EnableSceneContainer
+ public void showPrimaryBouncer_attemptDeviceEntry() {
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
+ verify(mDeviceEntryInteractor).attemptDeviceEntry();
+ }
+
+ @Test
public void altBouncerNotVisible_keyguardAuthenticatedBiometricsHandled() {
clearInvocations(mAlternateBouncerInteractor);
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
deleted file mode 100644
index ef10fdf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.service;
-
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class PersistentConnectionManagerTest extends SysuiTestCase {
- private static final int MAX_RETRIES = 5;
- private static final int RETRY_DELAY_MS = 1000;
- private static final int CONNECTION_MIN_DURATION_MS = 5000;
- private static final String DUMPSYS_NAME = "dumpsys_name";
-
- private FakeSystemClock mFakeClock = new FakeSystemClock();
- private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
-
- @Mock
- private ObservableServiceConnection<Proxy> mConnection;
-
- @Mock
- private ObservableServiceConnection.Callback<Proxy> mConnectionCallback;
-
- @Mock
- private Observer mObserver;
-
- @Mock
- private DumpManager mDumpManager;
-
- private static class Proxy {
- }
-
- private PersistentConnectionManager<Proxy> mConnectionManager;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mConnectionManager = new PersistentConnectionManager<>(
- mFakeClock,
- mFakeExecutor,
- mDumpManager,
- DUMPSYS_NAME,
- mConnection,
- MAX_RETRIES,
- RETRY_DELAY_MS,
- CONNECTION_MIN_DURATION_MS,
- mObserver);
- }
-
- private ObservableServiceConnection.Callback<Proxy> captureCallbackAndSend(
- ObservableServiceConnection<Proxy> mConnection, Proxy proxy) {
- ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
- ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
-
- verify(mConnection).addCallback(connectionCallbackCaptor.capture());
- verify(mConnection).bind();
- Mockito.clearInvocations(mConnection);
-
- final ObservableServiceConnection.Callback callback = connectionCallbackCaptor.getValue();
- if (proxy != null) {
- callback.onConnected(mConnection, proxy);
- } else {
- callback.onDisconnected(mConnection, 0);
- }
-
- return callback;
- }
-
- /**
- * Validates initial connection.
- */
- @Test
- public void testConnect() {
- mConnectionManager.start();
- captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
- }
-
- /**
- * Ensures reconnection on disconnect.
- */
- @Test
- public void testRetryOnBindFailure() {
- mConnectionManager.start();
- ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
- ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
-
- verify(mConnection).addCallback(connectionCallbackCaptor.capture());
-
- // Verify attempts happen. Note that we account for the retries plus initial attempt, which
- // is not scheduled.
- for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
- verify(mConnection).bind();
- Mockito.clearInvocations(mConnection);
- connectionCallbackCaptor.getValue().onDisconnected(mConnection, 0);
- mFakeExecutor.advanceClockToNext();
- mFakeExecutor.runAllReady();
- }
- }
-
- /**
- * Ensures manual unbind does not reconnect.
- */
- @Test
- public void testStopDoesNotReconnect() {
- mConnectionManager.start();
- ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
- ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
-
- verify(mConnection).addCallback(connectionCallbackCaptor.capture());
- verify(mConnection).bind();
- Mockito.clearInvocations(mConnection);
- mConnectionManager.stop();
- mFakeExecutor.advanceClockToNext();
- mFakeExecutor.runAllReady();
- verify(mConnection, never()).bind();
- }
-
- /**
- * Ensures rebind on package change.
- */
- @Test
- public void testAttemptOnPackageChange() {
- mConnectionManager.start();
- verify(mConnection).bind();
- ArgumentCaptor<Observer.Callback> callbackCaptor =
- ArgumentCaptor.forClass(Observer.Callback.class);
- captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
-
- verify(mObserver).addCallback(callbackCaptor.capture());
-
- callbackCaptor.getValue().onSourceChanged();
- verify(mConnection).bind();
- }
-
- @Test
- public void testAddConnectionCallback() {
- mConnectionManager.addConnectionCallback(mConnectionCallback);
- verify(mConnection).addCallback(mConnectionCallback);
- }
-
- @Test
- public void testRemoveConnectionCallback() {
- mConnectionManager.removeConnectionCallback(mConnectionCallback);
- verify(mConnection).removeCallback(mConnectionCallback);
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt
new file mode 100644
index 0000000..a7b5873
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 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.app.admin
+
+import android.app.AlarmManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.alarmManager by Kosmos.Fixture { mock<AlarmManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt
new file mode 100644
index 0000000..f51e122
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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 com.android.app.admin
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.devicePolicyManager by Kosmos.Fixture { mock<android.app.admin.DevicePolicyManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
index d9ea5e9..b511270 100644
--- a/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
@@ -16,7 +16,14 @@
package com.android.internal.widget
+import android.app.admin.devicePolicyManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
-var Kosmos.lockPatternUtils by Kosmos.Fixture { mock<LockPatternUtils>() }
+var Kosmos.lockPatternUtils by
+ Kosmos.Fixture {
+ mock<LockPatternUtils>().apply {
+ whenever(this.devicePolicyManager).thenReturn(this@Fixture.devicePolicyManager)
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index a1021f6..f436a68 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
+import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
@@ -39,6 +40,15 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.settings.displayTracker
+import com.android.systemui.util.icons.fakeAppCategoryIconProvider
+
+var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSource by
+ Kosmos.Fixture {
+ AppCategoriesShortcutsSource(
+ fakeAppCategoryIconProvider,
+ mainResources,
+ )
+ }
var Kosmos.shortcutHelperSystemShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { SystemShortcutsSource(mainResources) }
@@ -67,6 +77,7 @@
testDispatcher,
shortcutHelperSystemShortcutsSource,
shortcutHelperMultiTaskingShortcutsSource,
+ shortcutHelperAppCategoriesShortcutsSource,
shortcutHelperInputShortcutsSource,
fakeInputManager.inputManager,
shortcutHelperStateRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 5bae6ec..87143ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -135,6 +135,9 @@
private var isShowKeyguardWhenReenabled: Boolean = false
+ private val _canIgnoreAuthAndReturnToGone = MutableStateFlow(false)
+ override val canIgnoreAuthAndReturnToGone = _canIgnoreAuthAndReturnToGone.asStateFlow()
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
@@ -278,6 +281,10 @@
override fun isShowKeyguardWhenReenabled(): Boolean {
return isShowKeyguardWhenReenabled
}
+
+ override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) {
+ _canIgnoreAuthAndReturnToGone.value = canWake
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index ae138c8..ef789d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -37,5 +37,6 @@
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
deviceEntryRepository = deviceEntryRepository,
+ wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index e7e007f..446652c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -39,5 +39,6 @@
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
deviceEntryRepository = deviceEntryRepository,
+ wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index a9be06d..6c3de44 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -16,13 +16,16 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+@OptIn(ExperimentalCoroutinesApi::class)
var Kosmos.fromDreamingTransitionInteractor by
Kosmos.Fixture {
FromDreamingTransitionInteractor(
@@ -36,5 +39,6 @@
glanceableHubTransitions = glanceableHubTransitions,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
new file mode 100644
index 0000000..63e168d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 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 com.android.systemui.keyguard.domain.interactor
+
+import android.app.admin.alarmManager
+import android.content.mockedContext
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.keyguardWakeDirectlyToGoneInteractor by
+ Kosmos.Fixture {
+ KeyguardWakeDirectlyToGoneInteractor(
+ applicationCoroutineScope,
+ mockedContext,
+ fakeKeyguardRepository,
+ systemClock,
+ alarmManager,
+ keyguardTransitionInteractor,
+ powerInteractor,
+ fakeSettings,
+ lockPatternUtils,
+ fakeSettings,
+ selectedUserInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index bd9c0be..8bb2fce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
@@ -25,6 +26,7 @@
Kosmos.Fixture {
WindowManagerLockscreenVisibilityInteractor(
keyguardInteractor = keyguardInteractor,
+ transitionRepository = keyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
fromLockscreenInteractor = fromLockscreenTransitionInteractor,
@@ -33,5 +35,6 @@
notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
sceneInteractor = { sceneInteractor },
deviceEntryInteractor = { deviceEntryInteractor },
+ wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/StatusBarChipsLogKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/StatusBarChipsLogKosmos.kt
new file mode 100644
index 0000000..e904cd2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/StatusBarChipsLogKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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 com.android.systemui.statusbar.chips
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.statusBarChipsLogger by Kosmos.Fixture { logcatLogBuffer("StatusBarChips") }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt
index 064e57b..fcd14d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt
@@ -17,7 +17,15 @@
package com.android.systemui.statusbar.chips.call.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
val Kosmos.callChipInteractor: CallChipInteractor by
- Kosmos.Fixture { CallChipInteractor(repository = ongoingCallRepository) }
+ Kosmos.Fixture {
+ CallChipInteractor(
+ scope = applicationCoroutineScope,
+ repository = ongoingCallRepository,
+ logger = statusBarChipsLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index a8de460..144fe26 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -28,6 +29,7 @@
Kosmos.Fixture {
CastToOtherDeviceChipViewModel(
scope = applicationCoroutineScope,
+ context = applicationContext,
mediaProjectionChipInteractor = mediaProjectionChipInteractor,
mediaRouterChipInteractor = mediaRouterChipInteractor,
systemClock = fakeSystemClock,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt
index 6812a9d..0bfd88fc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
val Kosmos.mediaProjectionChipInteractor: MediaProjectionChipInteractor by
Kosmos.Fixture {
@@ -27,5 +28,6 @@
scope = applicationCoroutineScope,
mediaProjectionRepository = fakeMediaProjectionRepository,
packageManager = packageManager,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
index 4f82662..1ed7a47 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view
-import android.content.applicationContext
import android.content.packageManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
@@ -26,6 +25,5 @@
EndMediaProjectionDialogHelper(
dialogFactory = mockSystemUIDialogFactory,
packageManager = packageManager,
- context = applicationContext,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorKosmos.kt
index b4e9c14..557d3e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
val Kosmos.screenRecordChipInteractor: ScreenRecordChipInteractor by
Kosmos.Fixture {
@@ -27,5 +28,6 @@
scope = applicationCoroutineScope,
screenRecordRepository = screenRecordRepository,
mediaProjectionRepository = fakeMediaProjectionRepository,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index 99b7ec9..1d06947 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -27,6 +28,7 @@
Kosmos.Fixture {
ScreenRecordChipViewModel(
scope = applicationCoroutineScope,
+ context = applicationContext,
interactor = screenRecordChipInteractor,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
dialogTransitionAnimator = mockDialogTransitionAnimator,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 535f81a..2e475a3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -27,6 +28,7 @@
Kosmos.Fixture {
ShareToAppChipViewModel(
scope = applicationCoroutineScope,
+ context = applicationContext,
mediaProjectionChipInteractor = mediaProjectionChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
index 078e845..16e288f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.castToOtherDeviceChipViewModel
import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.screenRecordChipViewModel
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
val Kosmos.ongoingActivityChipsViewModel: OngoingActivityChipsViewModel by
Kosmos.Fixture {
@@ -31,5 +32,6 @@
shareToAppChipViewModel = shareToAppChipViewModel,
castToOtherDeviceChipViewModel = castToOtherDeviceChipViewModel,
callChipViewModel = callChipViewModel,
+ logger = statusBarChipsLogger,
)
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index d3efa21..9fc64a9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -26,6 +26,8 @@
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
+import android.hardware.input.InputManager;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
@@ -54,6 +56,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.StringJoiner;
/**
@@ -158,6 +161,13 @@
*/
static final int FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP = 0x00001000;
+ /**
+ * Flag for enabling the Accessibility mouse key events feature.
+ *
+ * @see #setUserAndEnabledFeatures(int, int)
+ */
+ static final int FLAG_FEATURE_MOUSE_KEYS = 0x00002000;
+
static final int FEATURES_AFFECTING_MOTION_EVENTS =
FLAG_FEATURE_INJECT_MOTION_EVENTS
| FLAG_FEATURE_AUTOCLICK
@@ -189,6 +199,8 @@
private KeyboardInterceptor mKeyboardInterceptor;
+ private MouseKeysInterceptor mMouseKeysInterceptor;
+
private boolean mInstalled;
private int mUserId;
@@ -733,6 +745,15 @@
// default display.
addFirstEventHandler(Display.DEFAULT_DISPLAY, mKeyboardInterceptor);
}
+
+ if ((mEnabledFeatures & FLAG_FEATURE_MOUSE_KEYS) != 0) {
+ mMouseKeysInterceptor = new MouseKeysInterceptor(mAms,
+ Objects.requireNonNull(mContext.getSystemService(
+ InputManager.class)),
+ Looper.myLooper(),
+ Display.DEFAULT_DISPLAY);
+ addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor);
+ }
}
/**
@@ -816,6 +837,11 @@
mKeyboardInterceptor.onDestroy();
mKeyboardInterceptor = null;
}
+
+ if (mMouseKeysInterceptor != null) {
+ mMouseKeysInterceptor.onDestroy();
+ mMouseKeysInterceptor = null;
+ }
}
private MagnificationGestureHandler createMagnificationGestureHandler(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 32491b7..b918d80 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -57,6 +57,7 @@
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.accessibilityservice.AccessibilityGestureEvent;
@@ -2936,6 +2937,9 @@
if (combinedGenericMotionEventSources != 0) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS;
}
+ if (userState.isMouseKeysEnabled()) {
+ flags |= AccessibilityInputFilter.FLAG_FEATURE_MOUSE_KEYS;
+ }
if (flags != 0) {
if (!mHasInputFilter) {
mHasInputFilter = true;
@@ -3216,6 +3220,7 @@
somethingChanged |= readMagnificationCapabilitiesLocked(userState);
somethingChanged |= readMagnificationFollowTypingLocked(userState);
somethingChanged |= readAlwaysOnMagnificationLocked(userState);
+ somethingChanged |= readMouseKeysEnabledLocked(userState);
return somethingChanged;
}
@@ -5476,6 +5481,9 @@
private final Uri mAlwaysOnMagnificationUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED);
+ private final Uri mMouseKeysUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -5524,6 +5532,8 @@
mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mAlwaysOnMagnificationUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mMouseKeysUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -5604,6 +5614,10 @@
readMagnificationFollowTypingLocked(userState);
} else if (mAlwaysOnMagnificationUri.equals(uri)) {
readAlwaysOnMagnificationLocked(userState);
+ } else if (mMouseKeysUri.equals(uri)) {
+ if (readMouseKeysEnabledLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
@@ -5742,6 +5756,20 @@
return false;
}
+ boolean readMouseKeysEnabledLocked(AccessibilityUserState userState) {
+ if (!keyboardA11yMouseKeys()) {
+ return false;
+ }
+ final boolean isMouseKeysEnabled =
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, 0, userState.mUserId) == 1;
+ if (isMouseKeysEnabled != userState.isMouseKeysEnabled()) {
+ userState.setMouseKeysEnabled(isMouseKeysEnabled);
+ return true;
+ }
+ return false;
+ }
+
@Override
public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
mMainHandler.sendMessage(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 7bcbc27..b061065 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -169,6 +169,8 @@
private final int mFocusStrokeWidthDefaultValue;
// The default value of the focus color.
private final int mFocusColorDefaultValue;
+ /** Whether mouse keys feature is enabled. */
+ private boolean mMouseKeysEnabled = false;
private final Map<ComponentName, ComponentName> mA11yServiceToTileService = new ArrayMap<>();
private final Map<ComponentName, ComponentName> mA11yActivityToTileService = new ArrayMap<>();
@@ -674,6 +676,14 @@
mIsFilterKeyEventsEnabled = enabled;
}
+ public void setMouseKeysEnabled(boolean enabled) {
+ mMouseKeysEnabled = enabled;
+ }
+
+ public boolean isMouseKeysEnabled() {
+ return mMouseKeysEnabled;
+ }
+
public int getInteractiveUiTimeoutLocked() {
return mInteractiveUiTimeout;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
new file mode 100644
index 0000000..3f0f23f
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -0,0 +1,498 @@
+/*
+ * 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 com.android.server.accessibility;
+
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
+import static android.util.MathUtils.sqrt;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.hardware.input.InputManager;
+import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+
+/**
+ * Implements the "mouse keys" accessibility feature for physical keyboards.
+ *
+ * If enabled, mouse keys will allow users to use a physical keyboard to
+ * control the mouse on the display.
+ * The following mouse functionality is supported by the mouse keys:
+ * <ul>
+ * <li> Move the mouse pointer in different directions (up, down, left, right and diagonally).
+ * <li> Click the mouse button (left, right and middle click).
+ * <li> Press and hold the mouse button.
+ * <li> Release the mouse button.
+ * <li> Scroll (up and down).
+ * </ul>
+ *
+ * The keys that are mapped to mouse keys are consumed by {@link AccessibilityInputFilter}.
+ * Non-mouse key {@link KeyEvent} will be passed to the parent handler to be handled as usual.
+ * A new {@link VirtualMouse} is created whenever the mouse keys feature is turned on in Settings.
+ * In case multiple physical keyboard are connected to a device,
+ * mouse keys of each physical keyboard will control a single (global) mouse pointer.
+ */
+public class MouseKeysInterceptor extends BaseEventStreamTransformation implements Handler.Callback,
+ InputManager.InputDeviceListener {
+ private static final String LOG_TAG = "MouseKeysInterceptor";
+
+ // To enable these logs, run: 'adb shell setprop log.tag.MouseKeysInterceptor DEBUG'
+ // (requires restart)
+ private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
+
+ private static final int MESSAGE_MOVE_MOUSE_POINTER = 1;
+ private static final int MESSAGE_SCROLL_MOUSE_POINTER = 2;
+ private static final float MOUSE_POINTER_MOVEMENT_STEP = 1.8f;
+ private static final int KEY_NOT_SET = -1;
+
+ /** Time interval after which mouse action will be repeated */
+ private static final int INTERVAL_MILLIS = 10;
+
+ private final AccessibilityManagerService mAms;
+ private final InputManager mInputManager;
+ private final Handler mHandler;
+
+ private final int mDisplayId;
+
+ VirtualDeviceManager.VirtualDevice mVirtualDevice = null;
+
+ private VirtualMouse mVirtualMouse = null;
+
+ /**
+ * State of the active directional mouse key.
+ * Multiple mouse keys will not be allowed to be used simultaneously i.e.,
+ * once a mouse key is pressed, other mouse key presses will be disregarded
+ * (except for when the "HOLD" key is pressed).
+ */
+ private int mActiveMoveKey = KEY_NOT_SET;
+
+ /** State of the active scroll mouse key. */
+ private int mActiveScrollKey = KEY_NOT_SET;
+
+ /** Last time the key action was performed */
+ private long mLastTimeKeyActionPerformed = 0;
+
+ // TODO (b/346706749): This is currently using the numpad key bindings for mouse keys.
+ // Decide the final mouse key bindings with UX input.
+ public enum MouseKeyEvent {
+ DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_1),
+ DOWN_MOVE(KeyEvent.KEYCODE_NUMPAD_2),
+ DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_3),
+ LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_4),
+ RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_6),
+ DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_7),
+ UP_MOVE(KeyEvent.KEYCODE_NUMPAD_8),
+ DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_9),
+ LEFT_CLICK(KeyEvent.KEYCODE_NUMPAD_5),
+ RIGHT_CLICK(KeyEvent.KEYCODE_NUMPAD_DOT),
+ HOLD(KeyEvent.KEYCODE_NUMPAD_MULTIPLY),
+ RELEASE(KeyEvent.KEYCODE_NUMPAD_SUBTRACT),
+ SCROLL_UP(KeyEvent.KEYCODE_A),
+ SCROLL_DOWN(KeyEvent.KEYCODE_S);
+
+ private final int mKeyCode;
+ MouseKeyEvent(int enumValue) {
+ mKeyCode = enumValue;
+ }
+
+ private static final SparseArray<MouseKeyEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
+
+ static {
+ for (MouseKeyEvent type : MouseKeyEvent.values()) {
+ VALUE_TO_ENUM_MAP.put(type.mKeyCode, type);
+ }
+ }
+
+ public final int getKeyCodeValue() {
+ return mKeyCode;
+ }
+
+ /**
+ * Convert int value of the key code to corresponding MouseEvent enum. If no matching
+ * value is found, this will return {@code null}.
+ */
+ @Nullable
+ public static MouseKeyEvent from(int value) {
+ return VALUE_TO_ENUM_MAP.get(value);
+ }
+ }
+
+ /**
+ * Construct a new MouseKeysInterceptor.
+ *
+ * @param service The service to notify of key events
+ * @param inputManager InputManager to track changes to connected input devices
+ * @param looper Looper to use for callbacks and messages
+ * @param displayId Display ID to send mouse events to
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public MouseKeysInterceptor(AccessibilityManagerService service, InputManager inputManager,
+ Looper looper, int displayId) {
+ mAms = service;
+ mInputManager = inputManager;
+ mHandler = new Handler(looper, this);
+ mInputManager.registerInputDeviceListener(this, mHandler);
+ mDisplayId = displayId;
+ // Create the virtual mouse on a separate thread since virtual device creation
+ // should happen on an auxiliary thread, and not from the handler's thread.
+ new Thread(() -> {
+ mVirtualMouse = createVirtualMouse();
+ }).start();
+
+ }
+
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void sendVirtualMouseRelativeEvent(float x, float y) {
+ if (mVirtualMouse != null) {
+ mVirtualMouse.sendRelativeEvent(new VirtualMouseRelativeEvent.Builder()
+ .setRelativeX(x)
+ .setRelativeY(y)
+ .build()
+ );
+ }
+ }
+
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void sendVirtualMouseButtonEvent(int buttonCode, int actionCode) {
+ if (mVirtualMouse != null) {
+ mVirtualMouse.sendButtonEvent(new VirtualMouseButtonEvent.Builder()
+ .setAction(actionCode)
+ .setButtonCode(buttonCode)
+ .build()
+ );
+ }
+ }
+
+ /**
+ * Performs a mouse scroll action based on the provided key code.
+ * This method interprets the key code as a mouse scroll and sends
+ * the corresponding {@code VirtualMouseScrollEvent#mYAxisMovement}.
+
+ * @param keyCode The key code representing the mouse scroll action.
+ * Supported keys are:
+ * <ul>
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_UP}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_DOWN}
+ * </ul>
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void performMouseScrollAction(int keyCode) {
+ MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+ float y = switch (mouseKeyEvent) {
+ case SCROLL_UP -> 1.0f;
+ case SCROLL_DOWN -> -1.0f;
+ default -> 0.0f;
+ };
+ if (mVirtualMouse != null) {
+ mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
+ .setYAxisMovement(y)
+ .build()
+ );
+ }
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
+ + " for scroll action with axis movement (y=" + y + ")");
+ }
+ }
+
+ /**
+ * Performs a mouse button action based on the provided key code.
+ * This method interprets the key code as a mouse button press and sends
+ * the corresponding press and release events to the virtual mouse.
+
+ * @param keyCode The key code representing the mouse button action.
+ * Supported keys are:
+ * <ul>
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT_CLICK} (Primary Button)
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT_CLICK} (Secondary
+ * Button)
+ * </ul>
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void performMouseButtonAction(int keyCode) {
+ MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+ int buttonCode = switch (mouseKeyEvent) {
+ case LEFT_CLICK -> VirtualMouseButtonEvent.BUTTON_PRIMARY;
+ case RIGHT_CLICK -> VirtualMouseButtonEvent.BUTTON_SECONDARY;
+ default -> VirtualMouseButtonEvent.BUTTON_UNKNOWN;
+ };
+ if (buttonCode != VirtualMouseButtonEvent.BUTTON_UNKNOWN) {
+ sendVirtualMouseButtonEvent(buttonCode, VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
+ sendVirtualMouseButtonEvent(buttonCode, VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
+ }
+ if (DEBUG) {
+ if (buttonCode == VirtualMouseButtonEvent.BUTTON_UNKNOWN) {
+ Slog.d(LOG_TAG, "Button code is unknown for mouse key event: "
+ + mouseKeyEvent.name());
+ } else {
+ Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
+ + " for button action");
+ }
+ }
+ }
+
+ /**
+ * Performs a mouse pointer action based on the provided key code.
+ * The method calculates the relative movement of the mouse pointer
+ * and sends the corresponding event to the virtual mouse.
+ *
+ * @param keyCode The key code representing the direction or button press.
+ * Supported keys are:
+ * <ul>
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_LEFT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DOWN}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_RIGHT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_LEFT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent UP}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_RIGHT}
+ * </ul>
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void performMousePointerAction(int keyCode) {
+ float x = 0f;
+ float y = 0f;
+ MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+ switch (mouseKeyEvent) {
+ case DIAGONAL_DOWN_LEFT_MOVE -> {
+ x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ }
+ case DOWN_MOVE -> {
+ y = MOUSE_POINTER_MOVEMENT_STEP;
+ }
+ case DIAGONAL_DOWN_RIGHT_MOVE -> {
+ x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ }
+ case LEFT_MOVE -> {
+ x = -MOUSE_POINTER_MOVEMENT_STEP;
+ }
+ case RIGHT_MOVE -> {
+ x = MOUSE_POINTER_MOVEMENT_STEP;
+ }
+ case DIAGONAL_UP_LEFT_MOVE -> {
+ x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ y = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ }
+ case UP_MOVE -> {
+ y = -MOUSE_POINTER_MOVEMENT_STEP;
+ }
+ case DIAGONAL_UP_RIGHT_MOVE -> {
+ x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ y = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ }
+ default -> {
+ x = 0.0f;
+ y = 0.0f;
+ }
+ }
+ sendVirtualMouseRelativeEvent(x, y);
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
+ + " for relative pointer movement (x=" + x + ", y=" + y + ")");
+ }
+ }
+
+ private boolean isMouseKey(int keyCode) {
+ return MouseKeyEvent.VALUE_TO_ENUM_MAP.contains(keyCode);
+ }
+
+ private boolean isMouseButtonKey(int keyCode) {
+ return keyCode == MouseKeyEvent.LEFT_CLICK.getKeyCodeValue()
+ || keyCode == MouseKeyEvent.RIGHT_CLICK.getKeyCodeValue();
+ }
+
+ private boolean isMouseScrollKey(int keyCode) {
+ return keyCode == MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
+ || keyCode == MouseKeyEvent.SCROLL_DOWN.getKeyCodeValue();
+ }
+
+ /**
+ * Create a virtual mouse using the VirtualDeviceManagerInternal.
+ *
+ * @return The created VirtualMouse.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private VirtualMouse createVirtualMouse() {
+ final VirtualDeviceManagerInternal localVdm =
+ LocalServices.getService(VirtualDeviceManagerInternal.class);
+ mVirtualDevice = localVdm.createVirtualDevice(
+ new VirtualDeviceParams.Builder().setName("Mouse Keys Virtual Device").build());
+ VirtualMouse virtualMouse = mVirtualDevice.createVirtualMouse(
+ new VirtualMouseConfig.Builder()
+ .setInputDeviceName("Mouse Keys Virtual Mouse")
+ .setAssociatedDisplayId(mDisplayId)
+ .build());
+ return virtualMouse;
+ }
+
+ /**
+ * Handles key events and forwards mouse key events to the virtual mouse.
+ *
+ * @param event The key event to handle.
+ * @param policyFlags The policy flags associated with the key event.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @Override
+ public void onKeyEvent(KeyEvent event, int policyFlags) {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(FLAGS_INPUT_FILTER)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onKeyEvent",
+ FLAGS_INPUT_FILTER, "event=" + event + ";policyFlags=" + policyFlags);
+ }
+ boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
+ int keyCode = event.getKeyCode();
+
+ if (!isMouseKey(keyCode)) {
+ // Pass non-mouse key events to the next handler
+ super.onKeyEvent(event, policyFlags);
+ } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
+ sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
+ VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
+ } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
+ sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
+ VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
+ } else if (isDown && isMouseButtonKey(keyCode)) {
+ performMouseButtonAction(keyCode);
+ } else if (isDown && isMouseScrollKey(keyCode)) {
+ // If the scroll key is pressed down and no other key is active,
+ // set it as the active key and send a message to scroll the pointer
+ if (mActiveScrollKey == KEY_NOT_SET) {
+ mActiveScrollKey = keyCode;
+ mLastTimeKeyActionPerformed = event.getDownTime();
+ mHandler.sendEmptyMessage(MESSAGE_SCROLL_MOUSE_POINTER);
+ }
+ } else if (isDown) {
+ // This is a directional key.
+ // If the key is pressed down and no other key is active,
+ // set it as the active key and send a message to move the pointer
+ if (mActiveMoveKey == KEY_NOT_SET) {
+ mActiveMoveKey = keyCode;
+ mLastTimeKeyActionPerformed = event.getDownTime();
+ mHandler.sendEmptyMessage(MESSAGE_MOVE_MOUSE_POINTER);
+ }
+ } else if (mActiveMoveKey == keyCode) {
+ // If the key is released, and it is the active key, stop moving the pointer
+ mActiveMoveKey = KEY_NOT_SET;
+ mHandler.removeMessages(MESSAGE_MOVE_MOUSE_POINTER);
+ } else if (mActiveScrollKey == keyCode) {
+ // If the key is released, and it is the active key, stop scrolling the pointer
+ mActiveScrollKey = KEY_NOT_SET;
+ mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
+ } else {
+ Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
+ + "', with no matching down event from deviceId = " + event.getDeviceId());
+ }
+ }
+
+ /**
+ * Handle messages for moving or scrolling the mouse pointer.
+ *
+ * @param msg The message to handle.
+ * @return True if the message was handled, false otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_MOVE_MOUSE_POINTER ->
+ handleMouseMessage(msg.getWhen(), mActiveMoveKey, MESSAGE_MOVE_MOUSE_POINTER);
+ case MESSAGE_SCROLL_MOUSE_POINTER ->
+ handleMouseMessage(msg.getWhen(), mActiveScrollKey,
+ MESSAGE_SCROLL_MOUSE_POINTER);
+ default -> {
+ Slog.e(LOG_TAG, "Unexpected message type");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Handles mouse-related messages for moving or scrolling the mouse pointer.
+ * This method checks if the specified time interval {@code INTERVAL_MILLIS} has passed since
+ * the last movement or scroll action and performs the corresponding action if necessary.
+ * If there is an active key, the message is rescheduled to be handled again
+ * after the specified {@code INTERVAL_MILLIS}.
+ *
+ * @param currentTime The current time when the message is being handled.
+ * @param activeKey The key code representing the active key. This determines
+ * the direction or type of action to be performed.
+ * @param messageType The type of message to be handled. It can be one of the
+ * following:
+ * <ul>
+ * <li>{@link #MESSAGE_MOVE_MOUSE_POINTER} - for moving the mouse pointer.
+ * <li>{@link #MESSAGE_SCROLL_MOUSE_POINTER} - for scrolling mouse pointer.
+ * </ul>
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void handleMouseMessage(long currentTime, int activeKey, int messageType) {
+ if (currentTime - mLastTimeKeyActionPerformed >= INTERVAL_MILLIS) {
+ if (messageType == MESSAGE_MOVE_MOUSE_POINTER) {
+ performMousePointerAction(activeKey);
+ } else if (messageType == MESSAGE_SCROLL_MOUSE_POINTER) {
+ performMouseScrollAction(activeKey);
+ }
+ mLastTimeKeyActionPerformed = currentTime;
+ }
+ if (activeKey != KEY_NOT_SET) {
+ // Reschedule the message if the key is still active
+ mHandler.sendEmptyMessageDelayed(messageType, INTERVAL_MILLIS);
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ }
+
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @Override
+ public void onDestroy() {
+ // Clear mouse state
+ mActiveMoveKey = KEY_NOT_SET;
+ mActiveScrollKey = KEY_NOT_SET;
+ mLastTimeKeyActionPerformed = 0;
+ mHandler.removeCallbacksAndMessages(null);
+
+ mVirtualDevice.close();
+ mInputManager.unregisterInputDeviceListener(this);
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ }
+
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6d9b4f5..0d309eb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -977,7 +977,7 @@
private NotificationManager mNm;
private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
- private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
+ private volatile VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
private long mLoweredFromNormalToVibrateTime;
// Array of Uids of valid assistant services to check if caller is one of them
@@ -12101,6 +12101,11 @@
}
}
+ @Override
+ public VolumePolicy getVolumePolicy() {
+ return mVolumePolicy;
+ }
+
/** Interface used for enforcing the safe hearing standard. */
public interface ISafeHearingVolumeController {
/** Displays an instructional safeguard as required by the safe hearing standard. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index eb78fe6..a118415 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.biometrics.BiometricConstants;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -28,11 +27,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.modules.expresslog.Counter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
-import java.util.function.BooleanSupplier;
+
/**
* Contains all the necessary information for a HAL operation.
@@ -89,8 +89,6 @@
private final BaseClientMonitor mClientMonitor;
@Nullable
private final ClientMonitorCallback mClientCallback;
- @NonNull
- private final BooleanSupplier mIsDebuggable;
@Nullable
private ClientMonitorCallback mOnStartCallback;
@OperationState
@@ -99,6 +97,7 @@
@NonNull
final Runnable mCancelWatchdog;
+ @VisibleForTesting
BiometricSchedulerOperation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable ClientMonitorCallback callback
@@ -106,33 +105,14 @@
this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
}
- @VisibleForTesting
- BiometricSchedulerOperation(
- @NonNull BaseClientMonitor clientMonitor,
- @Nullable ClientMonitorCallback callback,
- @NonNull BooleanSupplier isDebuggable
- ) {
- this(clientMonitor, callback, STATE_WAITING_IN_QUEUE, isDebuggable);
- }
-
protected BiometricSchedulerOperation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable ClientMonitorCallback callback,
@OperationState int state
) {
- this(clientMonitor, callback, state, Build::isDebuggable);
- }
-
- private BiometricSchedulerOperation(
- @NonNull BaseClientMonitor clientMonitor,
- @Nullable ClientMonitorCallback callback,
- @OperationState int state,
- @NonNull BooleanSupplier isDebuggable
- ) {
mClientMonitor = clientMonitor;
mClientCallback = callback;
mState = state;
- mIsDebuggable = isDebuggable;
mCancelWatchdog = () -> {
if (!isFinished()) {
Slog.e(TAG, "[Watchdog Triggered]: " + this);
@@ -187,9 +167,7 @@
if (mClientMonitor.getCookie() != 0) {
String err = "operation requires cookie";
- if (mIsDebuggable.getAsBoolean()) {
- throw new IllegalStateException(err);
- }
+ Counter.logIncrement("biometric.value_biometric_scheduler_operation_state_error_count");
Slog.e(TAG, err);
}
@@ -456,10 +434,9 @@
private boolean errorWhenOneOf(String op, @OperationState int... states) {
final boolean isError = ArrayUtils.contains(states, mState);
if (isError) {
- String err = op + ": mState must not be " + mState;
- if (mIsDebuggable.getAsBoolean()) {
- throw new IllegalStateException(err);
- }
+ Counter.logIncrement(
+ "biometric.value_biometric_scheduler_operation_state_error_count");
+ final String err = op + ": mState must not be " + mState;
Slog.e(TAG, err);
}
return isError;
@@ -468,10 +445,10 @@
private boolean errorWhenNoneOf(String op, @OperationState int... states) {
final boolean isError = !ArrayUtils.contains(states, mState);
if (isError) {
- String err = op + ": mState=" + mState + " must be one of " + Arrays.toString(states);
- if (mIsDebuggable.getAsBoolean()) {
- throw new IllegalStateException(err);
- }
+ Counter.logIncrement(
+ "biometric.value_biometric_scheduler_operation_state_error_count");
+ final String err = op + ": mState=" + mState + " must be one of "
+ + Arrays.toString(states);
Slog.e(TAG, err);
}
return isError;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 6928b33..3493381b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4841,6 +4841,8 @@
}
final int[] supportedStates =
mDeviceStateManager.getSupportedStateIdentifiers();
+ // TODO(b/352019542): remove the log once b/345960547 is fixed.
+ Slog.d(TAG, "supportedStates=" + Arrays.toString(supportedStates));
DisplayInfo displayInfo;
for (int state : supportedStates) {
displayInfo = mLogicalDisplayMapper.getDisplayInfoForStateLocked(state,
@@ -4849,6 +4851,8 @@
possibleInfo.add(displayInfo);
}
}
+ // TODO(b/352019542): remove the log once b/345960547 is fixed.
+ Slog.d(TAG, "possibleInfos=" + possibleInfo);
return possibleInfo;
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 9eef657..e46397b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -62,6 +62,8 @@
return showNotification();
case "cancel-notifications":
return cancelNotifications();
+ case "get-brightness":
+ return getBrightness();
case "set-brightness":
return setBrightness();
case "reset-brightness-configuration":
@@ -313,6 +315,25 @@
return 0;
}
+ private int getBrightness() {
+ String displayIdString = getNextArg();
+ if (displayIdString == null) {
+ getErrPrintWriter().println("Error: no display id specified");
+ return 1;
+ }
+ int displayId;
+ try {
+ displayId = Integer.parseInt(displayIdString);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid displayId=" + displayIdString + " not int");
+ return 1;
+ }
+ final Context context = mService.getContext();
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ getOutPrintWriter().println(dm.getBrightness(displayId));
+ return 0;
+ }
+
private int setBrightness() {
String brightnessText = getNextArg();
if (brightnessText == null) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index e645e98..4791cd1 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -389,11 +389,15 @@
// Retrieve the layout for this particular state.
final Layout layout = mDeviceStateToLayoutMap.get(deviceState);
if (layout == null) {
+ // TODO(b/352019542): remove the log once b/345960547 is fixed.
+ Slog.d(TAG, "Cannot get layout for given state:" + deviceState);
return null;
}
// Retrieve the details of the given display within this layout.
Layout.Display display = layout.getById(displayId);
if (display == null) {
+ // TODO(b/352019542): remove the log once b/345960547 is fixed.
+ Slog.d(TAG, "Cannot get display for given layout:" + layout);
return null;
}
// Retrieve the display info for the display that matches the display id.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b0d734d..0dbaaf3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2521,6 +2521,7 @@
hideStatusBarIconLocked();
getUserData(userId).mInFullscreenMode = false;
mWindowManagerInternal.setDismissImeOnBackKeyPressed(false);
+ scheduleResetStylusHandwriting();
}
@BinderThread
diff --git a/services/core/java/com/android/server/location/altitude/AltitudeService.java b/services/core/java/com/android/server/location/altitude/AltitudeService.java
index 289d4a2..96540c2 100644
--- a/services/core/java/com/android/server/location/altitude/AltitudeService.java
+++ b/services/core/java/com/android/server/location/altitude/AltitudeService.java
@@ -25,6 +25,7 @@
import android.location.Location;
import android.location.altitude.AltitudeConverter;
import android.os.RemoteException;
+import android.util.Log;
import com.android.server.SystemService;
@@ -38,6 +39,8 @@
*/
public class AltitudeService extends IAltitudeService.Stub {
+ private static final String TAG = "AltitudeService";
+
private final AltitudeConverter mAltitudeConverter = new AltitudeConverter();
private final Context mContext;
@@ -59,6 +62,7 @@
try {
mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
} catch (IOException e) {
+ Log.e(TAG, "", e);
response.success = false;
return response;
}
@@ -74,6 +78,7 @@
try {
return mAltitudeConverter.getGeoidHeight(mContext, request);
} catch (IOException e) {
+ Log.e(TAG, "", e);
GetGeoidHeightResponse response = new GetGeoidHeightResponse();
response.success = false;
return response;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
index 2ec9bdb..3aea6d5 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
@@ -33,11 +33,11 @@
@ContextHubTransaction.Type
private final int mTransactionType;
- private final Long mNanoAppId;
+ private final long mNanoAppId;
private final String mPackage;
- private final Integer mMessageSequenceNumber;
+ private final int mMessageSequenceNumber;
private long mNextRetryTime;
@@ -53,9 +53,9 @@
ContextHubServiceTransaction(int id, int type, String packageName) {
mTransactionId = id;
mTransactionType = type;
- mNanoAppId = null;
+ mNanoAppId = Long.MAX_VALUE;
mPackage = packageName;
- mMessageSequenceNumber = null;
+ mMessageSequenceNumber = Integer.MAX_VALUE;
mNextRetryTime = Long.MAX_VALUE;
mTimeoutTime = Long.MAX_VALUE;
mNumCompletedStartCalls = 0;
@@ -68,7 +68,7 @@
mTransactionType = type;
mNanoAppId = nanoAppId;
mPackage = packageName;
- mMessageSequenceNumber = null;
+ mMessageSequenceNumber = Integer.MAX_VALUE;
mNextRetryTime = Long.MAX_VALUE;
mTimeoutTime = Long.MAX_VALUE;
mNumCompletedStartCalls = 0;
@@ -79,7 +79,7 @@
int messageSequenceNumber, short hostEndpointId) {
mTransactionId = id;
mTransactionType = type;
- mNanoAppId = null;
+ mNanoAppId = Long.MAX_VALUE;
mPackage = packageName;
mMessageSequenceNumber = messageSequenceNumber;
mNextRetryTime = Long.MAX_VALUE;
@@ -131,7 +131,7 @@
return mTransactionType;
}
- Integer getMessageSequenceNumber() {
+ int getMessageSequenceNumber() {
return mMessageSequenceNumber;
}
@@ -204,14 +204,14 @@
out.append(ContextHubTransaction.typeToString(mTransactionType,
/* upperCase= */ true));
out.append(" (");
- if (mNanoAppId != null) {
+ if (mNanoAppId != Long.MAX_VALUE) {
out.append("appId = 0x");
out.append(Long.toHexString(mNanoAppId));
out.append(", ");
}
out.append("package = ");
out.append(mPackage);
- if (mMessageSequenceNumber != null) {
+ if (mMessageSequenceNumber != Integer.MAX_VALUE) {
out.append(", messageSequenceNumber = ");
out.append(mMessageSequenceNumber);
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index 1a449e0..e6d330f8 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -474,9 +474,8 @@
return;
}
- Integer transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
+ int transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE
- || transactionMessageSequenceNumber == null
|| transactionMessageSequenceNumber != messageSequenceNumber) {
Log.w(TAG, "Received unexpected message transaction response (expected message "
+ "sequence number = "
@@ -494,7 +493,8 @@
ContextHubServiceTransaction transaction =
mReliableMessageTransactionMap.get(messageSequenceNumber);
if (transaction == null) {
- Log.w(TAG, "Could not find reliable message transaction with message sequence number"
+ Log.w(TAG, "Could not find reliable message transaction with "
+ + "message sequence number = "
+ messageSequenceNumber);
return;
}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 614a0a5..5a9cf03 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -138,6 +138,7 @@
private final boolean mUseAttentionLight;
boolean mHasLight;
+ private final boolean mEnableNotificationAccessibilityEvents;
private final SettingsObserver mSettingsObserver;
@@ -190,6 +191,9 @@
mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight);
mHasLight =
resources.getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed);
+ mEnableNotificationAccessibilityEvents =
+ resources.getBoolean(
+ com.android.internal.R.bool.config_enableNotificationAccessibilityEvents);
// Don't start allowing notifications until the setup wizard has run once.
// After that, including subsequent boots, init with notifications turned on.
@@ -1030,7 +1034,7 @@
}
void sendAccessibilityEvent(NotificationRecord record) {
- if (!mAccessibilityManager.isEnabled()) {
+ if (!mAccessibilityManager.isEnabled() || !mEnableNotificationAccessibilityEvents) {
return;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0cda30f..9d0c0e9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3568,24 +3568,6 @@
Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
+ " interceptKeyBeforeQueueing");
return true;
- case KeyEvent.KEYCODE_VIDEO_APP_1:
- case KeyEvent.KEYCODE_VIDEO_APP_2:
- case KeyEvent.KEYCODE_VIDEO_APP_3:
- case KeyEvent.KEYCODE_VIDEO_APP_4:
- case KeyEvent.KEYCODE_VIDEO_APP_5:
- case KeyEvent.KEYCODE_VIDEO_APP_6:
- case KeyEvent.KEYCODE_VIDEO_APP_7:
- case KeyEvent.KEYCODE_VIDEO_APP_8:
- case KeyEvent.KEYCODE_FEATURED_APP_1:
- case KeyEvent.KEYCODE_FEATURED_APP_2:
- case KeyEvent.KEYCODE_FEATURED_APP_3:
- case KeyEvent.KEYCODE_FEATURED_APP_4:
- case KeyEvent.KEYCODE_DEMO_APP_1:
- case KeyEvent.KEYCODE_DEMO_APP_2:
- case KeyEvent.KEYCODE_DEMO_APP_3:
- case KeyEvent.KEYCODE_DEMO_APP_4:
- Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
- return true;
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
if (down) {
@@ -5496,9 +5478,8 @@
// In case startedGoingToSleep is called after screenTurnedOff (the source caller is in
// order but the methods run on different threads) and updateScreenOffSleepToken was
// skipped. Then acquire sleep token if screen was off.
- if (!mDefaultDisplayPolicy.isScreenOnFully() && !mDefaultDisplayPolicy.isScreenOnEarly()
- && com.android.window.flags.Flags.skipSleepingWhenSwitchingDisplay()) {
- updateScreenOffSleepToken(true /* acquire */, false /* isSwappingDisplay */);
+ if (!mDefaultDisplayPolicy.isScreenOnFully() && !mDefaultDisplayPolicy.isScreenOnEarly()) {
+ updateScreenOffSleepToken(true /* acquire */);
}
if (mKeyguardDelegate != null) {
@@ -5661,9 +5642,8 @@
if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off...");
if (displayId == DEFAULT_DISPLAY) {
- if (!isSwappingDisplay || mIsGoingToSleepDefaultDisplay
- || !com.android.window.flags.Flags.skipSleepingWhenSwitchingDisplay()) {
- updateScreenOffSleepToken(true /* acquire */, isSwappingDisplay);
+ if (!isSwappingDisplay || mIsGoingToSleepDefaultDisplay) {
+ updateScreenOffSleepToken(true /* acquire */);
}
mRequestedOrSleepingDefaultDisplay = false;
mDefaultDisplayPolicy.screenTurnedOff();
@@ -5722,7 +5702,7 @@
if (displayId == DEFAULT_DISPLAY) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
0 /* cookie */);
- updateScreenOffSleepToken(false /* acquire */, false /* isSwappingDisplay */);
+ updateScreenOffSleepToken(false /* acquire */);
mDefaultDisplayPolicy.screenTurningOn(screenOnListener);
mBootAnimationDismissable = false;
@@ -6228,9 +6208,9 @@
}
// TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
- private void updateScreenOffSleepToken(boolean acquire, boolean isSwappingDisplay) {
+ private void updateScreenOffSleepToken(boolean acquire) {
if (acquire) {
- mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY, isSwappingDisplay);
+ mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY);
} else {
mScreenOffSleepTokenAcquirer.release(DEFAULT_DISPLAY);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b0f92e8..129cee7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8998,16 +8998,6 @@
return inTransitionSelfOrParent();
}
- boolean isDisplaySleepingAndSwapping() {
- for (int i = mDisplayContent.mAllSleepTokens.size() - 1; i >= 0; i--) {
- RootWindowContainer.SleepToken sleepToken = mDisplayContent.mAllSleepTokens.get(i);
- if (sleepToken.isDisplaySwapping()) {
- return true;
- }
- }
- return false;
- }
-
/**
* Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
* orientation then aspect ratio restrictions are also already respected.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index c088118..3b0b727 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -145,13 +145,6 @@
void acquire(int displayId);
/**
- * Acquires a sleep token.
- * @param displayId The display to apply to.
- * @param isSwappingDisplay Whether the display is swapping to another physical display.
- */
- void acquire(int displayId, boolean isSwappingDisplay);
-
- /**
* Releases the sleep token.
* @param displayId The display to apply to.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ded205e..5b17875 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5056,16 +5056,10 @@
@Override
public void acquire(int displayId) {
- acquire(displayId, false /* isSwappingDisplay */);
- }
-
- @Override
- public void acquire(int displayId, boolean isSwappingDisplay) {
synchronized (mGlobalLock) {
if (!mSleepTokens.contains(displayId)) {
mSleepTokens.append(displayId,
- mRootWindowContainer.createSleepToken(mTag, displayId,
- isSwappingDisplay));
+ mRootWindowContainer.createSleepToken(mTag, displayId));
updateSleepIfNeededLocked();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 00b6453..e81b440 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -38,6 +38,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.ACTION_VIEW;
import static android.content.pm.PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -121,6 +122,7 @@
import android.graphics.Rect;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManagerInternal;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -142,6 +144,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
+import android.webkit.URLUtil;
import android.window.ActivityWindowInfo;
import com.android.internal.R;
@@ -158,6 +161,7 @@
import com.android.server.pm.SaferIntentUtils;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
+import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -189,9 +193,6 @@
// How long we can hold the launch wake lock before giving up.
private static final int LAUNCH_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
- // How long we delay processing the stopping and finishing activities.
- private static final int SCHEDULE_FINISHING_STOPPING_ACTIVITY_MS = 200;
-
/** How long we wait until giving up on the activity telling us it released the top state. */
private static final int TOP_RESUMED_STATE_LOSS_TIMEOUT = 500;
@@ -2093,7 +2094,6 @@
boolean processPausingActivities, String reason) {
// Stop any activities that are scheduled to do so but have been waiting for the transition
// animation to finish.
- boolean displaySwapping = false;
ArrayList<ActivityRecord> readyToStopActivities = null;
for (int i = 0; i < mStoppingActivities.size(); i++) {
final ActivityRecord s = mStoppingActivities.get(i);
@@ -2101,10 +2101,9 @@
// send onStop before any configuration change when removing pip transition is ongoing.
final boolean animating = s.isInTransition()
&& s.getTask() != null && !s.getTask().isForceHidden();
- displaySwapping |= s.isDisplaySleepingAndSwapping();
ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
+ "finishing=%s", s, s.nowVisible, animating, s.finishing);
- if ((!animating && !displaySwapping) || mService.mShuttingDown
+ if (!animating || mService.mShuttingDown
|| s.getRootTask().isForceHiddenForPinnedTask()) {
if (!processPausingActivities && s.isState(PAUSING)) {
// Defer processing pausing activities in this iteration and reschedule
@@ -2125,16 +2124,6 @@
}
}
- // Stopping activities are deferred processing if the display is swapping. Check again
- // later to ensure the stopping activities can be stopped after display swapped.
- if (displaySwapping) {
- mHandler.postDelayed(() -> {
- synchronized (mService.mGlobalLock) {
- scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
- }
- }, SCHEDULE_FINISHING_STOPPING_ACTIVITY_MS);
- }
-
final int numReadyStops = readyToStopActivities == null ? 0 : readyToStopActivities.size();
for (int i = 0; i < numReadyStops; i++) {
final ActivityRecord r = readyToStopActivities.get(i);
@@ -2915,6 +2904,9 @@
@Override
public void accept(ActivityRecord r) {
+ if (Flags.enableDesktopWindowingAppToWeb() && mInfo.capturedLink == null) {
+ setCapturedLink(r);
+ }
if (r.mLaunchCookie != null) {
mInfo.addLaunchCookie(r.mLaunchCookie);
}
@@ -2927,6 +2919,16 @@
mTopRunning = r;
}
}
+
+ private void setCapturedLink(ActivityRecord r) {
+ final Uri uri = r.intent.getData();
+ if (uri == null || !ACTION_VIEW.equals(r.intent.getAction())
+ || !URLUtil.isNetworkUrl(uri.toString())) {
+ return;
+ }
+ mInfo.capturedLink = uri;
+ mInfo.capturedLinkTimestamp = r.lastLaunchTime;
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 30f2d0d..6abef8b 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.View.DRAG_FLAG_GLOBAL;
import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
@@ -217,6 +218,11 @@
mDragState.mToken = dragToken;
mDragState.mDisplayContent = displayContent;
mDragState.mData = data;
+ mDragState.mCallingTaskIdToHide = shouldMoveCallingTaskToBack(callingWin,
+ flags);
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Calling task to hide=" + mDragState.mCallingTaskIdToHide);
+ }
if ((flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) == 0) {
final Display display = displayContent.getDisplay();
@@ -364,6 +370,23 @@
}
/**
+ * If the calling window's task should be hidden for the duration of the drag, this returns the
+ * task id of the task (or -1 otherwise).
+ */
+ private int shouldMoveCallingTaskToBack(WindowState callingWin, int flags) {
+ if ((flags & View.DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) == 0) {
+ // Not requested by the app
+ return -1;
+ }
+ final ActivityRecord callingActivity = callingWin.getActivityRecord();
+ if (callingActivity == null || callingActivity.getTask() == null) {
+ // Not an activity
+ return -1;
+ }
+ return callingActivity.getTask().mTaskId;
+ }
+
+ /**
* Notifies the unhandled drag listener if needed.
* @return whether the listener was notified and subsequent drag completion should be deferred
* until the listener calls back
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 4be5bad..ba74f50 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
@@ -48,6 +49,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -117,6 +119,8 @@
InputInterceptor mInputInterceptor;
ArrayList<WindowState> mNotifiedWindows;
boolean mDragInProgress;
+ // Set to non -1 value if a valid app requests DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START
+ int mCallingTaskIdToHide;
/**
* Whether if animation is completed. Needs to be volatile to update from the animation thread
* without having a WM lock.
@@ -320,12 +324,12 @@
}
}
final boolean targetInterceptsGlobalDrag = targetInterceptsGlobalDrag(touchedWin);
- return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+ return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mDataDescription, mData,
/* includeDragSurface= */ targetInterceptsGlobalDrag,
/* includeDragFlags= */ targetInterceptsGlobalDrag,
dragAndDropPermissions);
} else {
- return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+ return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mDataDescription, mData,
/* includeDragSurface= */ includePrivateInfo,
/* includeDragFlags= */ includePrivateInfo,
null /* dragAndDropPermissions */);
@@ -527,11 +531,24 @@
Slog.d(TAG_WM, "Sending DRAG_STARTED to new window " + newWin);
}
// Only allow the extras to be dispatched to a global-intercepting drag target
- ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
+ ClipData data = null;
+ if (interceptsGlobalDrag) {
+ data = mData.copyForTransferWithActivityInfo();
+ PersistableBundle extras = data.getDescription().getExtras() != null
+ ? data.getDescription().getExtras()
+ : new PersistableBundle();
+ extras.putInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, mCallingTaskIdToHide);
+ // Note that setting extras always copies the bundle
+ data.getDescription().setExtras(extras);
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Adding EXTRA_HIDE_DRAG_SOURCE_TASK_ID=" + mCallingTaskIdToHide);
+ }
+ }
+ ClipDescription description = data != null ? data.getDescription() : mDataDescription;
DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
- data, false /* includeDragSurface */, true /* includeDragFlags */,
- null /* dragAndDropPermission */);
+ description, data, false /* includeDragSurface */,
+ true /* includeDragFlags */, null /* dragAndDropPermission */);
try {
newWin.mClient.dispatchDragEvent(event);
// track each window that we've notified that the drag is starting
@@ -700,37 +717,51 @@
return mDragInProgress;
}
- private DragEvent obtainDragEvent(int action, float x, float y, ClipData data,
- boolean includeDragSurface, boolean includeDragFlags,
+ private DragEvent obtainDragEvent(int action, float x, float y, ClipDescription description,
+ ClipData data, boolean includeDragSurface, boolean includeDragFlags,
IDragAndDropPermissions dragAndDropPermissions) {
return DragEvent.obtain(action, x, y, mThumbOffsetX, mThumbOffsetY,
includeDragFlags ? mFlags : 0,
- null /* localState */, mDataDescription, data,
+ null /* localState */, description, data,
includeDragSurface ? mSurfaceControl : null,
dragAndDropPermissions, false /* result */);
}
private ValueAnimator createReturnAnimationLocked() {
- final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
- mOriginalX - mThumbOffsetX),
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
- mOriginalY - mThumbOffsetY),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
- mAnimatedScale),
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
+ final ValueAnimator animator;
+ final long duration;
+ if (mCallingTaskIdToHide != -1) {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentX, mCurrentX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
+ mAnimatedScale),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+ duration = MIN_ANIMATION_DURATION_MS;
+ } else {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
+ mOriginalX - mThumbOffsetX),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
+ mOriginalY - mThumbOffsetY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
+ mAnimatedScale),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
- final float translateX = mOriginalX - mCurrentX;
- final float translateY = mOriginalY - mCurrentY;
- // Adjust the duration to the travel distance.
- final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY);
- final double displayDiagonal =
- Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
- final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
- * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
+ final float translateX = mOriginalX - mCurrentX;
+ final float translateY = mOriginalY - mCurrentY;
+ // Adjust the duration to the travel distance.
+ final double travelDistance = Math.sqrt(
+ translateX * translateX + translateY * translateY);
+ final double displayDiagonal =
+ Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
+ duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
+ * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
+ }
+
final AnimationListener listener = new AnimationListener();
animator.setDuration(duration);
animator.setInterpolator(mCubicEaseOutInterpolator);
@@ -742,13 +773,24 @@
}
private ValueAnimator createCancelAnimationLocked() {
- final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+ final ValueAnimator animator;
+ if (mCallingTaskIdToHide != -1) {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentX, mCurrentX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
+ mAnimatedScale),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+ } else {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+ }
+
final AnimationListener listener = new AnimationListener();
animator.setDuration(MIN_ANIMATION_DURATION_MS);
animator.setInterpolator(mCubicEaseOutInterpolator);
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 6f25280..7aede8b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -61,7 +61,6 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Environment;
import android.os.IBinder;
@@ -76,7 +75,6 @@
import android.util.SparseBooleanArray;
import android.view.InsetsState;
import android.view.MotionEvent;
-import android.view.WindowInsets;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import com.android.internal.annotations.VisibleForTesting;
@@ -1536,6 +1534,11 @@
return true;
}
+ // The task is marked as non-trimmable. Don't trim the task.
+ if (!task.mIsTrimmableFromRecents) {
+ return false;
+ }
+
// Ignore tasks from different displays
// TODO (b/115289124): No Recents on non-default displays.
if (!task.isOnHomeDisplay()) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d3fc7f3..99697de 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2847,10 +2847,6 @@
}
SleepToken createSleepToken(String tag, int displayId) {
- return createSleepToken(tag, displayId, false /* isSwappingDisplay */);
- }
-
- SleepToken createSleepToken(String tag, int displayId, boolean isSwappingDisplay) {
final DisplayContent display = getDisplayContent(displayId);
if (display == null) {
throw new IllegalArgumentException("Invalid display: " + displayId);
@@ -2859,7 +2855,7 @@
final int tokenKey = makeSleepTokenKey(tag, displayId);
SleepToken token = mSleepTokens.get(tokenKey);
if (token == null) {
- token = new SleepToken(tag, displayId, isSwappingDisplay);
+ token = new SleepToken(tag, displayId);
mSleepTokens.put(tokenKey, token);
display.mAllSleepTokens.add(token);
ProtoLog.d(WM_DEBUG_STATES, "Create sleep token: tag=%s, displayId=%d", tag, displayId);
@@ -3799,34 +3795,18 @@
private final String mTag;
private final long mAcquireTime;
private final int mDisplayId;
- private final boolean mIsSwappingDisplay;
final int mHashKey;
- // The display could remain in sleep after the physical display swapped, adding a 1
- // seconds display swap timeout to prevent activities staying in PAUSED state.
- // Otherwise, the sleep token should be removed once display turns back on after swapped.
- private static final long DISPLAY_SWAP_TIMEOUT = 1000;
-
- SleepToken(String tag, int displayId, boolean isSwappingDisplay) {
+ SleepToken(String tag, int displayId) {
mTag = tag;
mDisplayId = displayId;
mAcquireTime = SystemClock.uptimeMillis();
- mIsSwappingDisplay = isSwappingDisplay;
mHashKey = makeSleepTokenKey(mTag, mDisplayId);
}
- public boolean isDisplaySwapping() {
- long now = SystemClock.uptimeMillis();
- if (now - mAcquireTime > DISPLAY_SWAP_TIMEOUT) {
- return false;
- }
- return mIsSwappingDisplay;
- }
-
@Override
public String toString() {
return "{\"" + mTag + "\", display " + mDisplayId
- + (mIsSwappingDisplay ? " is swapping " : "")
+ ", acquire at " + TimeUtils.formatUptime(mAcquireTime) + "}";
}
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index b452131..8c7b637 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -442,7 +442,10 @@
return taskDisplayArea;
}
- private boolean isAssistant(ActivityTaskManagerService atmService, int callingUid) {
+ /**
+ * Returns whether the given UID caller is the assistant.
+ */
+ public static boolean isAssistant(ActivityTaskManagerService atmService, int callingUid) {
if (atmService.mActiveVoiceInteractionServiceComponent == null) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 310516b..75e3e65 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -349,7 +349,7 @@
final int callingPid = Binder.getCallingPid();
// Validate and resolve ClipDescription data before clearing the calling identity
validateAndResolveDragMimeTypeExtras(data, callingUid, callingPid, mPackageName);
- validateDragFlags(flags);
+ validateDragFlags(flags, callingUid);
final long ident = Binder.clearCallingIdentity();
try {
return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource,
@@ -375,12 +375,17 @@
* Validates the given drag flags.
*/
@VisibleForTesting
- void validateDragFlags(int flags) {
+ void validateDragFlags(int flags, int callingUid) {
if ((flags & View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
if (!mCanStartTasksFromRecents) {
throw new SecurityException("Requires START_TASKS_FROM_RECENTS permission");
}
}
+ if ((flags & View.DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) {
+ if (!SafeActivityOptions.isAssistant(mService.mAtmService, callingUid)) {
+ throw new SecurityException("Caller is not the assistant");
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 33a649b..79fec23 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -495,6 +495,12 @@
*/
boolean mReparentLeafTaskIfRelaunch;
+ /**
+ * Set to affect whether recents should be able to trim this task or not. It's set to true by
+ * default.
+ */
+ boolean mIsTrimmableFromRecents;
+
private final AnimatingActivityRegistry mAnimatingActivityRegistry =
new AnimatingActivityRegistry();
@@ -666,6 +672,7 @@
mLaunchCookie = _launchCookie;
mDeferTaskAppear = _deferTaskAppear;
mRemoveWithTaskOrganizer = _removeWithTaskOrganizer;
+ mIsTrimmableFromRecents = true;
EventLogTags.writeWmTaskCreated(mTaskId);
}
@@ -3856,6 +3863,7 @@
pw.print(" isResizeable="); pw.println(isResizeable());
pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
+ pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents);
}
@Override
@@ -6296,6 +6304,10 @@
}
}
+ void setTrimmableFromRecents(boolean isTrimmable) {
+ mIsTrimmableFromRecents = isTrimmable;
+ }
+
/**
* Return true if the activityInfo has the same requiredDisplayCategory as this task.
*/
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9a5f84c..8033122 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9299,7 +9299,8 @@
isTrustedOverlay);
final int sanitizedLpFlags =
- (flags & (FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE))
+ (flags & (FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE
+ | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH))
| LayoutParams.FLAG_NOT_TOUCH_MODAL;
h.layoutParamsType = type;
h.layoutParamsFlags = sanitizedLpFlags;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e900488..bc32b91 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -62,6 +62,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH;
@@ -123,8 +124,8 @@
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
@@ -1371,6 +1372,17 @@
task.setReparentLeafTaskIfRelaunch(hop.isReparentLeafTaskIfRelaunch());
break;
}
+ case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE: {
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ final Task task = container != null ? container.asTask() : null;
+ if (task == null || !task.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ + container);
+ break;
+ }
+ task.setTrimmableFromRecents(hop.isTrimmableFromRecents());
+ break;
+ }
}
return effects;
}
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index c76bcf8..3ed6ad7 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -404,7 +404,7 @@
String traceTag = traceInitialization ? "camera_init" : "camera";
BackgroundThread.get().getThreadHandler().postDelayed(() -> {
try {
- mIProfcollect.trace_system(traceTag);
+ mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider");
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 17d9ef9..f83144f 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -35,12 +35,15 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.IntentFilter;
import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerGlobal;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
@@ -182,6 +185,9 @@
doNothing().when(mContext).enforceCallingPermission(anyString(), anyString());
doNothing().when(mContext).sendBroadcastAsUser(any(), any());
doReturn(null).when(mContext).registerReceiver(any(), any());
+ doReturn(null).when(mContext).registerReceiver(
+ any(BroadcastReceiver.class),
+ any(IntentFilter.class), anyString(), any(Handler.class));
doReturn(null)
.when(mContext)
.registerReceiverAsUser(any(), any(), any(), anyString(), any(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
new file mode 100644
index 0000000..dc8d239
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -0,0 +1,272 @@
+/*
+ * 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 com.android.server.accessibility
+
+import android.companion.virtual.VirtualDeviceManager
+import android.companion.virtual.VirtualDeviceParams
+import android.content.Context
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.hardware.input.VirtualMouse
+import android.hardware.input.VirtualMouseButtonEvent
+import android.hardware.input.VirtualMouseConfig
+import android.hardware.input.VirtualMouseRelativeEvent
+import android.hardware.input.VirtualMouseScrollEvent
+import android.os.RemoteException
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal
+import com.android.server.LocalServices
+import com.android.server.testutils.OffsettableClock
+import junit.framework.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import java.util.concurrent.TimeUnit
+import java.util.LinkedList
+import java.util.Queue
+import android.util.ArraySet
+
+/**
+ * Tests for {@link MouseKeysInterceptor}
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:MouseKeysInterceptorTest
+ */
+@Presubmit
+class MouseKeysInterceptorTest {
+ companion object {
+ const val DISPLAY_ID = 1
+ const val DEVICE_ID = 123
+ // This delay is required for key events to be sent and handled correctly.
+ // The handler only performs a move/scroll event if it receives the key event
+ // at INTERVAL_MILLIS (which happens in practice). Hence, we need this delay in the tests.
+ const val KEYBOARD_POST_EVENT_DELAY_MILLIS = 20L
+ }
+
+ private lateinit var mouseKeysInterceptor: MouseKeysInterceptor
+ private val clock = OffsettableClock()
+ private val testLooper = TestLooper { clock.now() }
+ private val nextInterceptor = TrackingInterceptor()
+
+ @Mock
+ private lateinit var mockAms: AccessibilityManagerService
+
+ @Mock
+ private lateinit var iInputManager: IInputManager
+ private lateinit var testSession: InputManagerGlobal.TestSession
+ private lateinit var mockInputManager: InputManager
+
+ @Mock
+ private lateinit var mockVirtualDeviceManagerInternal: VirtualDeviceManagerInternal
+ @Mock
+ private lateinit var mockVirtualDevice: VirtualDeviceManager.VirtualDevice
+ @Mock
+ private lateinit var mockVirtualMouse: VirtualMouse
+
+ @Mock
+ private lateinit var mockTraceManager: AccessibilityTraceManager
+
+ @Before
+ @Throws(RemoteException::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ testSession = InputManagerGlobal.createTestSession(iInputManager)
+ mockInputManager = InputManager(context)
+
+ Mockito.`when`(mockVirtualDeviceManagerInternal.getDeviceIdsForUid(Mockito.anyInt()))
+ .thenReturn(ArraySet(setOf(DEVICE_ID)))
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal::class.java)
+ LocalServices.addService<VirtualDeviceManagerInternal>(
+ VirtualDeviceManagerInternal::class.java, mockVirtualDeviceManagerInternal
+ )
+
+ Mockito.`when`(mockVirtualDeviceManagerInternal.createVirtualDevice(
+ Mockito.any(VirtualDeviceParams::class.java)
+ )).thenReturn(mockVirtualDevice)
+ Mockito.`when`(mockVirtualDevice.createVirtualMouse(
+ Mockito.any(VirtualMouseConfig::class.java)
+ )).thenReturn(mockVirtualMouse)
+
+ Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+ Mockito.`when`(mockAms.traceManager).thenReturn(mockTraceManager)
+
+ mouseKeysInterceptor = MouseKeysInterceptor(mockAms, mockInputManager,
+ testLooper.looper, DISPLAY_ID)
+ // VirtualMouse is created on a separate thread.
+ // Wait for VirtualMouse to be created before running tests
+ TimeUnit.MILLISECONDS.sleep(20L)
+ mouseKeysInterceptor.next = nextInterceptor
+ }
+
+ @After
+ fun tearDown() {
+ testLooper.dispatchAll()
+ if (this::testSession.isInitialized) {
+ testSession.close()
+ }
+ }
+
+ @Test
+ fun whenNonMouseKeyEventArrives_eventIsPassedToNextInterceptor() {
+ val downTime = clock.now()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_Q, 0, 0, DEVICE_ID, 0)
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ assertEquals(1, nextInterceptor.events.size)
+ assertEquals(downEvent, nextInterceptor.events.poll())
+ }
+
+ @Test
+ fun whenMouseDirectionalKeyIsPressed_relativeEventIsSent() {
+ // There should be some delay between the downTime of the key event and calling onKeyEvent
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.DOWN_MOVE.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendRelativeEvent method is called once and capture the arguments
+ verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(1.8f))
+ }
+
+ @Test
+ fun whenClickKeyIsPressed_buttonEventIsSent() {
+ // There should be some delay between the downTime of the key event and calling onKeyEvent
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.LEFT_CLICK.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ val actions = arrayOf<Int>(
+ VirtualMouseButtonEvent.ACTION_BUTTON_PRESS,
+ VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE)
+ val buttons = arrayOf<Int>(
+ VirtualMouseButtonEvent.BUTTON_PRIMARY,
+ VirtualMouseButtonEvent.BUTTON_PRIMARY)
+ // Verify the sendButtonEvent method is called twice and capture the arguments
+ verifyButtonEvents(actions, buttons)
+ }
+
+ @Test
+ fun whenHoldKeyIsPressed_buttonEventIsSent() {
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.HOLD.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendButtonEvent method is called once and capture the arguments
+ verifyButtonEvents(
+ arrayOf<Int>( VirtualMouseButtonEvent.ACTION_BUTTON_PRESS),
+ arrayOf<Int>( VirtualMouseButtonEvent.BUTTON_PRIMARY)
+ )
+ }
+
+ @Test
+ fun whenReleaseKeyIsPressed_buttonEventIsSent() {
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.RELEASE.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendButtonEvent method is called once and capture the arguments
+ verifyButtonEvents(
+ arrayOf<Int>( VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE),
+ arrayOf<Int>( VirtualMouseButtonEvent.BUTTON_PRIMARY)
+ )
+ }
+
+ @Test
+ fun whenScrollUpKeyIsPressed_scrollEventIsSent() {
+ // There should be some delay between the downTime of the key event and calling onKeyEvent
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendScrollEvent method is called once and capture the arguments
+ verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+ }
+
+ private fun verifyRelativeEvents(expectedX: Array<Float>, expectedY: Array<Float>) {
+ assertEquals(expectedX.size, expectedY.size)
+ val captor = ArgumentCaptor.forClass(VirtualMouseRelativeEvent::class.java)
+ Mockito.verify(mockVirtualMouse, Mockito.times(expectedX.size))
+ .sendRelativeEvent(captor.capture())
+
+ for (i in expectedX.indices) {
+ val captorEvent = captor.allValues[i]
+ assertEquals(expectedX[i], captorEvent.relativeX)
+ assertEquals(expectedY[i], captorEvent.relativeY)
+ }
+ }
+
+ private fun verifyButtonEvents(actions: Array<Int>, buttons: Array<Int>) {
+ assertEquals(actions.size, buttons.size)
+ val captor = ArgumentCaptor.forClass(VirtualMouseButtonEvent::class.java)
+ Mockito.verify(mockVirtualMouse, Mockito.times(actions.size))
+ .sendButtonEvent(captor.capture())
+
+ for (i in actions.indices) {
+ val captorEvent = captor.allValues[i]
+ assertEquals(actions[i], captorEvent.action)
+ assertEquals(buttons[i], captorEvent.buttonCode)
+ }
+ }
+
+ private fun verifyScrollEvents(xAxisMovements: Array<Float>, yAxisMovements: Array<Float>) {
+ assertEquals(xAxisMovements.size, yAxisMovements.size)
+ val captor = ArgumentCaptor.forClass(VirtualMouseScrollEvent::class.java)
+ Mockito.verify(mockVirtualMouse, Mockito.times(xAxisMovements.size))
+ .sendScrollEvent(captor.capture())
+
+ for (i in xAxisMovements.indices) {
+ val captorEvent = captor.allValues[i]
+ assertEquals(xAxisMovements[i], captorEvent.xAxisMovement)
+ assertEquals(yAxisMovements[i], captorEvent.yAxisMovement)
+ }
+ }
+
+ private class TrackingInterceptor : BaseEventStreamTransformation() {
+ val events: Queue<KeyEvent> = LinkedList()
+
+ override fun onKeyEvent(event: KeyEvent, policyFlags: Int) {
+ events.add(event)
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index f6da411..ffc7811 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -28,7 +28,6 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
@@ -88,16 +87,14 @@
private Handler mHandler;
private BiometricSchedulerOperation mInterruptableOperation;
private BiometricSchedulerOperation mNonInterruptableOperation;
- private boolean mIsDebuggable;
@Before
public void setUp() {
mHandler = new Handler(TestableLooper.get(this).getLooper());
- mIsDebuggable = false;
mInterruptableOperation = new BiometricSchedulerOperation(mInterruptableClientMonitor,
- mClientCallback, () -> mIsDebuggable);
+ mClientCallback);
mNonInterruptableOperation = new BiometricSchedulerOperation(mNonInterruptableClientMonitor,
- mClientCallback, () -> mIsDebuggable);
+ mClientCallback);
when(mInterruptableClientMonitor.isInterruptable()).thenReturn(true);
when(mNonInterruptableClientMonitor.isInterruptable()).thenReturn(false);
@@ -143,32 +140,13 @@
}
@Test
- public void testSecondStartWithCookieCrashesWhenDebuggable() {
+ public void testSecondStartWithCookieFails() {
final int cookie = 5;
- mIsDebuggable = true;
when(mInterruptableClientMonitor.getCookie()).thenReturn(cookie);
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- final boolean started = mInterruptableOperation.startWithCookie(mOnStartCallback, cookie);
- assertThat(started).isTrue();
-
- assertThrows(IllegalStateException.class,
- () -> mInterruptableOperation.startWithCookie(mOnStartCallback, cookie));
- }
-
- @Test
- public void testSecondStartWithCookieFailsNicelyWhenNotDebuggable() {
- final int cookie = 5;
- mIsDebuggable = false;
- when(mInterruptableClientMonitor.getCookie()).thenReturn(cookie);
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- final boolean started = mInterruptableOperation.startWithCookie(mOnStartCallback, cookie);
- assertThat(started).isTrue();
-
- final boolean startedAgain = mInterruptableOperation.startWithCookie(mOnStartCallback,
- cookie);
- assertThat(startedAgain).isFalse();
+ assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isTrue();
+ assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isFalse();
}
@Test
@@ -217,56 +195,23 @@
}
@Test
- public void secondStartCrashesWhenDebuggable() {
- mIsDebuggable = true;
+ public void secondStartFails() {
when(mInterruptableClientMonitor.getCookie()).thenReturn(0);
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- final boolean started = mInterruptableOperation.start(mOnStartCallback);
- assertThat(started).isTrue();
-
- assertThrows(IllegalStateException.class, () -> mInterruptableOperation.start(
- mOnStartCallback));
- }
-
- @Test
- public void secondStartFailsNicelyWhenNotDebuggable() {
- mIsDebuggable = false;
- when(mInterruptableClientMonitor.getCookie()).thenReturn(0);
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- final boolean started = mInterruptableOperation.start(mOnStartCallback);
- assertThat(started).isTrue();
-
- final boolean startedAgain = mInterruptableOperation.start(mOnStartCallback);
- assertThat(startedAgain).isFalse();
+ assertThat(mInterruptableOperation.start(mOnStartCallback)).isTrue();
+ assertThat(mInterruptableOperation.start(mOnStartCallback)).isFalse();
}
@Test
public void doesNotStartWithCookie() {
- // This class only throws exceptions when debuggable.
- mIsDebuggable = true;
when(mInterruptableClientMonitor.getCookie()).thenReturn(9);
- assertThrows(IllegalStateException.class,
- () -> mInterruptableOperation.start(mock(ClientMonitorCallback.class)));
+
+ assertThat(mInterruptableOperation.start(mock(ClientMonitorCallback.class))).isFalse();
}
@Test
- public void cannotRestart() {
- // This class only throws exceptions when debuggable.
- mIsDebuggable = true;
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- mInterruptableOperation.start(mOnStartCallback);
-
- assertThrows(IllegalStateException.class,
- () -> mInterruptableOperation.start(mock(ClientMonitorCallback.class)));
- }
-
- @Test
- public void abortsNotRunning() {
- // This class only throws exceptions when debuggable.
- mIsDebuggable = true;
+ public void abortSuccessfulIfOperationNotRunning() {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
mInterruptableOperation.abort();
@@ -274,28 +219,17 @@
assertThat(mInterruptableOperation.isFinished()).isTrue();
verify(mInterruptableClientMonitor).unableToStart();
verify(mInterruptableClientMonitor).destroy();
- assertThrows(IllegalStateException.class,
- () -> mInterruptableOperation.start(mock(ClientMonitorCallback.class)));
+ assertThat(mInterruptableOperation.start(mock(ClientMonitorCallback.class))).isFalse();
}
@Test
- public void abortCrashesWhenDebuggableIfOperationIsRunning() {
- mIsDebuggable = true;
+ public void abortFailsIfOperationIsRunning() {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
mInterruptableOperation.start(mOnStartCallback);
-
- assertThrows(IllegalStateException.class, () -> mInterruptableOperation.abort());
- }
-
- @Test
- public void abortFailsNicelyWhenNotDebuggableIfOperationIsRunning() {
- mIsDebuggable = false;
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- mInterruptableOperation.start(mOnStartCallback);
-
mInterruptableOperation.abort();
+
+ assertThat(mInterruptableOperation.isFinished()).isFalse();
}
@Test
@@ -344,21 +278,7 @@
}
@Test
- public void cancelCrashesWhenDebuggableIfOperationIsFinished() {
- mIsDebuggable = true;
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- mInterruptableOperation.abort();
- assertThat(mInterruptableOperation.isFinished()).isTrue();
-
- final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
- assertThrows(IllegalStateException.class, () -> mInterruptableOperation.cancel(mHandler,
- cancelCb));
- }
-
- @Test
- public void cancelFailsNicelyWhenNotDebuggableIfOperationIsFinished() {
- mIsDebuggable = false;
+ public void cancelFailsIfOperationIsFinished() {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
mInterruptableOperation.abort();
@@ -366,6 +286,9 @@
final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
mInterruptableOperation.cancel(mHandler, cancelCb);
+
+ verify(mInterruptableClientMonitor, never()).cancel();
+ verify(mInterruptableClientMonitor, never()).cancelWithoutStarting(any());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 2233aa2..28a5db9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -227,6 +227,8 @@
when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(true);
when(resources.getBoolean(
com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(true);
+ when(resources.getBoolean(R.bool.config_enableNotificationAccessibilityEvents))
+ .thenReturn(true);
when(getContext().getResources()).thenReturn(resources);
// TODO (b/291907312): remove feature flag
@@ -2830,6 +2832,34 @@
assertThat(r.getRankingTimeMs()).isEqualTo(r.getSbn().getPostTime());
}
+ @Test
+ public void testAccessibilityEventsEnabledInConfig() throws Exception {
+ Resources resources = spy(getContext().getResources());
+ when(resources.getBoolean(R.bool.config_enableNotificationAccessibilityEvents))
+ .thenReturn(true);
+ when(getContext().getResources()).thenReturn(resources);
+ initAttentionHelper(mTestFlagResolver);
+ NotificationRecord r = getBeepyNotification();
+
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+ verify(mAccessibilityService).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
+ public void testAccessibilityEventsDisabledInConfig() throws Exception {
+ Resources resources = spy(getContext().getResources());
+ when(resources.getBoolean(R.bool.config_enableNotificationAccessibilityEvents))
+ .thenReturn(false);
+ when(getContext().getResources()).thenReturn(resources);
+ initAttentionHelper(mTestFlagResolver);
+ NotificationRecord r = getBeepyNotification();
+
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+ verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt());
+ }
+
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
private final int mRepeatIndex;
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 2480913..07934ea 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -131,8 +131,6 @@
@Test
public void testScreenTurnedOff() {
- mSetFlagsRule.enableFlags(com.android.window.flags.Flags
- .FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY);
doNothing().when(mPhoneWindowManager).updateSettings(any());
doNothing().when(mPhoneWindowManager).initializeHdmiState();
final boolean[] isScreenTurnedOff = { false };
@@ -159,14 +157,15 @@
// Skip sleep-token for non-sleep-screen-off.
clearInvocations(tokenAcquirer);
mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */);
- verify(tokenAcquirer, never()).acquire(anyInt(), anyBoolean());
+ verify(tokenAcquirer, never()).acquire(anyInt());
assertThat(isScreenTurnedOff[0]).isTrue();
// Apply sleep-token for sleep-screen-off.
+ isScreenTurnedOff[0] = false;
mPhoneWindowManager.startedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */);
assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isTrue();
mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */);
- verify(tokenAcquirer).acquire(eq(DEFAULT_DISPLAY), eq(true));
+ verify(tokenAcquirer).acquire(eq(DEFAULT_DISPLAY));
mPhoneWindowManager.finishedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */);
assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isFalse();
@@ -176,11 +175,11 @@
isScreenTurnedOff[0] = false;
clearInvocations(tokenAcquirer);
mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */);
- verify(tokenAcquirer, never()).acquire(anyInt(), anyBoolean());
+ verify(tokenAcquirer, never()).acquire(anyInt());
assertThat(displayPolicy.isScreenOnEarly()).isFalse();
assertThat(displayPolicy.isScreenOnFully()).isFalse();
mPhoneWindowManager.startedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */);
- verify(tokenAcquirer).acquire(eq(DEFAULT_DISPLAY), eq(false));
+ verify(tokenAcquirer).acquire(eq(DEFAULT_DISPLAY));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 7faf2aa..8cdb574 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -497,7 +497,8 @@
public void testValidateFlags() {
final Session session = getTestSession();
try {
- session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
+ session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
+ 0 /* callingUid */);
fail("Expected failure without permission");
} catch (SecurityException e) {
// Expected failure
@@ -510,7 +511,8 @@
.checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
final Session session = createTestSession(mAtm);
try {
- session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
+ session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
+ 0 /* callingUid */);
// Expected pass
} catch (SecurityException e) {
fail("Expected no failure with permission");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
index 7efbc88..35cff7a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -25,11 +25,11 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.LogLevel;
-import com.android.internal.protolog.ProtoLog;
import org.junit.After;
import org.junit.Ignore;
@@ -53,9 +53,6 @@
runWith(mockedProtoLog, this::testProtoLog);
verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(ProtoLogGroup.TEST_GROUP),
anyInt(), eq(0b0010010111),
- eq(com.android.internal.protolog.ProtoLogGroup.TEST_GROUP.isLogToLogcat()
- ? "Test completed successfully: %b %d %x %f %% %s"
- : null),
eq(new Object[]{true, 1L, 2L, 0.3, "ok"}));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 6ec1429..33f7035 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -653,6 +653,34 @@
}
@Test
+ public void testTrimNonTrimmableTask_isTrimmable_returnsFalse() {
+ Task nonTrimmableTask = createTaskBuilder(".NonTrimmableTask").setFlags(
+ FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS).build();
+ nonTrimmableTask.mIsTrimmableFromRecents = false;
+
+ // Move home to front so the task can satisfy the condition in RecentTasks#isTrimmable.
+ mRootWindowContainer.getDefaultTaskDisplayArea().getRootHomeTask().moveToFront("test");
+
+ assertThat(mRecentTasks.isTrimmable(nonTrimmableTask)).isFalse();
+ }
+
+ @Test
+ public void testTrimNonTrimmableTask_taskNotTrimmed() {
+ Task nonTrimmableTask = createTaskBuilder(".NonTrimmableTask").setFlags(
+ FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS).build();
+ nonTrimmableTask.mIsTrimmableFromRecents = false;
+
+ // Move home to front so the task can satisfy the condition in RecentTasks#isTrimmable.
+ mRootWindowContainer.getDefaultTaskDisplayArea().getRootHomeTask().moveToFront("test");
+
+ mRecentTasks.add(mTasks.get(0));
+ mRecentTasks.add(nonTrimmableTask);
+ mRecentTasks.add(mTasks.get(1));
+
+ triggerTrimAndAssertNoTasksTrimmed();
+ }
+
+ @Test
public void testSessionDuration() {
mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(-1 /* min */, -1 /* max */, 50 /* ms */);
diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
index 796c82d..3e6f743 100644
--- a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
+++ b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
@@ -43,12 +43,17 @@
/** carrier id */
private int mCarrierId;
+ /** apn */
+ private String mNiddApn;
+
/**
* @hide
*/
- public ProvisionSubscriberId(@NonNull String subscriberId, @NonNull int carrierId) {
+ public ProvisionSubscriberId(@NonNull String subscriberId, @NonNull int carrierId,
+ @NonNull String niddApn) {
this.mCarrierId = carrierId;
this.mSubscriberId = subscriberId;
+ this.mNiddApn = niddApn;
}
private ProvisionSubscriberId(Parcel in) {
@@ -63,6 +68,7 @@
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeString(mSubscriberId);
out.writeInt(mCarrierId);
+ out.writeString(mNiddApn);
}
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@@ -89,7 +95,7 @@
}
/**
- * @return token.
+ * @return provision subscriberId.
* @hide
*/
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@@ -106,6 +112,15 @@
return mCarrierId;
}
+ /**
+ * @return niddApn.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public String getNiddApn() {
+ return mNiddApn;
+ }
+
@NonNull
@Override
public String toString() {
@@ -117,12 +132,16 @@
sb.append("CarrierId:");
sb.append(mCarrierId);
+ sb.append(",");
+
+ sb.append("NiddApn:");
+ sb.append(mNiddApn);
return sb.toString();
}
@Override
public int hashCode() {
- return Objects.hash(mSubscriberId, mCarrierId);
+ return Objects.hash(mSubscriberId, mCarrierId, mNiddApn);
}
@Override
@@ -131,11 +150,12 @@
if (o == null || getClass() != o.getClass()) return false;
ProvisionSubscriberId that = (ProvisionSubscriberId) o;
return mSubscriberId.equals(that.mSubscriberId) && mCarrierId
- == that.mCarrierId;
+ == that.mCarrierId && mNiddApn.equals(that.mNiddApn);
}
private void readFromParcel(Parcel in) {
mSubscriberId = in.readString();
mCarrierId = in.readInt();
+ mNiddApn = in.readString();
}
}
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index 5a27593..5a48327 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -59,7 +59,6 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedList;
-import java.util.TreeMap;
/**
* Test class for {@link ProtoLogImpl}.
@@ -90,7 +89,7 @@
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024, new TreeMap<>(), () -> {});
+ 1024 * 1024, mReader, 1024, () -> {});
}
@After
@@ -142,7 +141,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{true, 10000, 30000, "test", 0.000003});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -159,7 +158,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{true, 10000, 0.0001, 0.00002, "test"});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -176,7 +175,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -192,7 +191,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -208,7 +207,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{5});
verify(implSpy, never()).passToLogcat(any(), any(), any());
@@ -277,7 +276,7 @@
long before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b1110101001010100, null,
+ 0b1110101001010100,
new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
long after = SystemClock.elapsedRealtimeNanos();
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
@@ -302,7 +301,7 @@
long before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b01100100, null,
+ 0b01100100,
new Object[]{"test", 1, 0.1, true});
long after = SystemClock.elapsedRealtimeNanos();
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
@@ -326,7 +325,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
mProtoLog.startProtoLog(mock(PrintWriter.class));
mProtoLog.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b11, null, new Object[]{true});
+ 0b11, new Object[]{true});
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
try (InputStream is = new FileInputStream(mFile)) {
ProtoInputStream ip = new ProtoInputStream(is);
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 1d7b6b3..b6672a0 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -60,6 +60,9 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import perfetto.protos.Protolog;
+import perfetto.protos.ProtologCommon;
+
import java.io.File;
import java.io.IOException;
import java.util.List;
@@ -67,9 +70,6 @@
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
-import perfetto.protos.Protolog;
-import perfetto.protos.ProtologCommon;
-
/**
* Test class for {@link ProtoLogImpl}.
*/
@@ -111,6 +111,9 @@
//noinspection ResultOfMethodCallIgnored
mFile.delete();
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
mViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder()
.addGroups(
Protolog.ProtoLogViewerConfig.Group.newBuilder()
@@ -157,8 +160,9 @@
mCacheUpdater = () -> {};
mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
mProtoLog = new PerfettoProtoLogImpl(
- viewerConfigInputStreamProvider, mReader, new TreeMap<>(),
+ viewerConfigInputStreamProvider, mReader,
() -> mCacheUpdater.run());
+ mProtoLog.registerGroups(TestProtoLogGroup.values());
}
@After
@@ -210,15 +214,15 @@
// Shouldn't be logging anything except WTF unless explicitly requested in the group
// override.
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -240,15 +244,15 @@
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -274,15 +278,15 @@
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -304,15 +308,15 @@
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ LogDataType.BOOLEAN, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -329,14 +333,14 @@
}
@Test
- public void log_logcatEnabledExternalMessage() {
+ public void log_logcatEnabled() {
when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{true, 10000, 30000, "test", 0.000003});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -353,32 +357,17 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{true, 10000, 0.0001, 0.00002, "test"});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
LogLevel.INFO),
- eq("UNKNOWN MESSAGE (1234) true 10000 1.0E-4 2.0E-5 test"));
+ eq("FORMAT_ERROR \"test %b %d %% %x %s %f\", "
+ + "args=(true, 10000, 1.0E-4, 2.0E-5, test)"));
verify(mReader).getViewerString(eq(1234L));
}
@Test
- public void log_logcatEnabledInlineMessage() {
- when(mReader.getViewerString(anyLong())).thenReturn("test %d");
- PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
- TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
- TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
-
- implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
- new Object[]{5});
-
- verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
- LogLevel.INFO), eq("test 5"));
- verify(mReader, never()).getViewerString(anyLong());
- }
-
- @Test
public void log_logcatEnabledNoMessage() {
when(mReader.getViewerString(anyLong())).thenReturn(null);
PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
@@ -386,11 +375,11 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
- LogLevel.INFO), eq("UNKNOWN MESSAGE (1234) 5"));
+ LogLevel.INFO), eq("UNKNOWN MESSAGE#1234 (5)"));
verify(mReader).getViewerString(eq(1234L));
}
@@ -401,7 +390,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{5});
verify(implSpy, never()).passToLogcat(any(), any(), any());
@@ -425,7 +414,7 @@
before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
- 0b1110101001010100, null,
+ 0b1110101001010100,
new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
after = SystemClock.elapsedRealtimeNanos();
} finally {
@@ -444,6 +433,38 @@
.isEqualTo("My test message :: test, 2, 4, 6, 0.400000, 5.000000e-01, 0.6, true");
}
+ @Test
+ public void log_noProcessing() throws IOException {
+ PerfettoTraceMonitor traceMonitor =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
+ long before;
+ long after;
+ try {
+ traceMonitor.start();
+ assertTrue(mProtoLog.isProtoEnabled());
+
+ before = SystemClock.elapsedRealtimeNanos();
+ mProtoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP,
+ "My test message :: %s, %d, %o, %x, %f, %b",
+ "test", 1, 2, 3, 0.4, true);
+ after = SystemClock.elapsedRealtimeNanos();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
+ .isAtLeast(before);
+ Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
+ .isAtMost(after);
+ Truth.assertThat(protolog.messages.getFirst().getMessage())
+ .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, true");
+ }
+
private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) {
final long messageId = new Random().nextLong();
mViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
@@ -470,7 +491,7 @@
before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
- 0b01100100, null,
+ 0b01100100,
new Object[]{"test", 1, 0.1, true});
after = SystemClock.elapsedRealtimeNanos();
} finally {
@@ -488,7 +509,7 @@
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- 0b11, null, new Object[]{true});
+ 0b11, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -512,7 +533,7 @@
ProtoLogImpl.setSingleInstance(mProtoLog);
ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1,
- 0b11, null, true);
+ 0b11, true);
} finally {
traceMonitor.stop(mWriter);
}
@@ -586,7 +607,7 @@
.isFalse();
Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse();
PerfettoTraceMonitor traceMonitor1 =
PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
@@ -664,7 +685,53 @@
Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isFalse();
Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
- .isTrue();
+ .isFalse();
+ }
+
+ @Test
+ public void supportsNullString() throws IOException {
+ PerfettoTraceMonitor traceMonitor =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true)
+ .build();
+
+ try {
+ traceMonitor.start();
+
+ mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "My test null string: %s", null);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("My test null string: null");
+ }
+
+ @Test
+ public void supportNullParams() throws IOException {
+ PerfettoTraceMonitor traceMonitor =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true)
+ .build();
+
+ try {
+ traceMonitor.start();
+
+ mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "My null args: %d, %f, %b", null, null, null);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("My null args: 0, 0, false");
}
private enum TestProtoLogGroup implements IProtoLogGroup {
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
index 60456f9..0496240 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -58,51 +58,50 @@
public void d_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.DEBUG), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
public void v_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.VERBOSE), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
public void i_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.INFO), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
public void w_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234,
- 4321, "test %d");
+ ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.WARN), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
public void e_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
@@ -110,10 +109,10 @@
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
ProtoLogImpl.wtf(TestProtoLogGroup.TEST_GROUP,
- 1234, 4321, "test %d");
+ 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.WTF), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
private enum TestProtoLogGroup implements IProtoLogGroup {
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
index 1087ae6..3c99e68 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
@@ -120,6 +120,8 @@
logCallVisitor?.processCall(call, messageString, getLevelForMethodName(
call.name.toString(), call, context), groupMap.getValue(groupName))
+ } else if (call.name.id == "initialize") {
+ // No processing
} else {
// Process non-log message calls
otherCallVisitor?.processCall(call)
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 2d5b50b..aa53005 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -443,10 +443,10 @@
val command = CommandOptions(args)
invoke(command)
} catch (ex: InvalidCommandException) {
- println("\n${ex.message}\n")
+ println("InvalidCommandException: \n${ex.message}\n")
showHelpAndExit()
} catch (ex: CodeProcessingException) {
- println("\n${ex.message}\n")
+ println("CodeProcessingException: \n${ex.message}\n")
exitProcess(1)
}
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index 6a8a071..c478f58 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -130,28 +130,27 @@
val hash = CodeUtils.hash(packagePath, messageString, level, group)
val newCall = call.clone()
- if (!group.textEnabled) {
- // Remove message string if text logging is not enabled by default.
- // Out: ProtoLog.e(GROUP, null, arg)
- newCall.arguments[1].replace(NameExpr("null"))
- }
+ // Remove message string.
+ // Out: ProtoLog.e(GROUP, args)
+ newCall.arguments.removeAt(1)
// Insert message string hash as a second argument.
- // Out: ProtoLog.e(GROUP, 1234, null, arg)
+ // Out: ProtoLog.e(GROUP, 1234, args)
newCall.arguments.add(1, LongLiteralExpr("" + hash + "L"))
val argTypes = LogDataType.parseFormatString(messageString)
val typeMask = LogDataType.logDataTypesToBitMask(argTypes)
// Insert bitmap representing which Number parameters are to be considered as
// floating point numbers.
- // Out: ProtoLog.e(GROUP, 1234, 0, null, arg)
+ // Out: ProtoLog.e(GROUP, 1234, 0, args)
newCall.arguments.add(2, IntegerLiteralExpr(typeMask))
// Replace call to a stub method with an actual implementation.
- // Out: ProtoLogImpl.e(GROUP, 1234, null, arg)
+ // Out: ProtoLogImpl.e(GROUP, 1234, 0, args)
newCall.setScope(protoLogImplClassNode)
if (argTypes.size != call.arguments.size - 2) {
throw InvalidProtoLogCallException(
"Number of arguments (${argTypes.size} does not match format" +
" string in: $call", ParsingContext(path, call))
}
+ val argsOffset = 3
val blockStmt = BlockStmt()
if (argTypes.isNotEmpty()) {
// Assign every argument to a variable to check its type in compile time
@@ -160,9 +159,9 @@
argTypes.forEachIndexed { idx, type ->
val varName = "protoLogParam$idx"
val declaration = VariableDeclarator(getASTTypeForDataType(type), varName,
- getConversionForType(type)(newCall.arguments[idx + 4].clone()))
+ getConversionForType(type)(newCall.arguments[idx + argsOffset].clone()))
blockStmt.addStatement(ExpressionStmt(VariableDeclarationExpr(declaration)))
- newCall.setArgument(idx + 4, NameExpr(SimpleName(varName)))
+ newCall.setArgument(idx + argsOffset, NameExpr(SimpleName(varName)))
}
} else {
// Assign (Object[])null as the vararg parameter to prevent allocating an empty
diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
index 2a83677..0cbbd48 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
@@ -60,7 +60,7 @@
.containsMatch(Pattern.compile("\\{ String protoLogParam0 = " +
"String\\.valueOf\\(argString\\); long protoLogParam1 = argInt; " +
"com\\.android\\.internal\\.protolog.ProtoLogImpl_.*\\.d\\(" +
- "GROUP, -6872339441335321086L, 4, null, protoLogParam0, protoLogParam1" +
+ "GROUP, -6872339441335321086L, 4, protoLogParam0, protoLogParam1" +
"\\); \\}"))
}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index 82aa93d..6cde7a7 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -76,7 +76,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -86,7 +86,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -98,8 +98,8 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -109,7 +109,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, "test", (Object[]) null); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, (Object[]) null); }
}
}
""".trimIndent()
@@ -119,7 +119,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, null, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -129,7 +129,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -172,13 +172,12 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(6, methodCall.arguments.size)
+ assertEquals(5, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
- assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
- assertEquals("protoLogParam0", methodCall.arguments[4].toString())
- assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[4].toString())
assertEquals(TRANSFORMED_CODE_TEXT_ENABLED, out)
}
@@ -214,13 +213,12 @@
Truth.assertThat(protoLogCalls).hasSize(3)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(6, methodCall.arguments.size)
+ assertEquals(5, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
- assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
- assertEquals("protoLogParam0", methodCall.arguments[4].toString())
- assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[4].toString())
assertEquals(TRANSFORMED_CODE_MULTICALL_TEXT, out)
}
@@ -252,13 +250,13 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(7, methodCall.arguments.size)
+ assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-4447034859795564700L", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[4].toString())
- assertEquals("protoLogParam1", methodCall.arguments[5].toString())
- assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[5].toString())
assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED, out)
}
@@ -289,7 +287,7 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(5, methodCall.arguments.size)
+ assertEquals(4, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("3218600869538902408L", methodCall.arguments[1].toString())
assertEquals(0.toString(), methodCall.arguments[2].toString())
@@ -323,13 +321,12 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(6, methodCall.arguments.size)
+ assertEquals(5, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
- assertEquals("null", methodCall.arguments[3].toString())
- assertEquals("protoLogParam0", methodCall.arguments[4].toString())
- assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[4].toString())
assertEquals(TRANSFORMED_CODE_TEXT_DISABLED, out)
}
@@ -361,14 +358,13 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(7, methodCall.arguments.size)
+ assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-4447034859795564700L", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
- assertEquals("null", methodCall.arguments[3].toString())
- assertEquals("protoLogParam0", methodCall.arguments[4].toString())
- assertEquals("protoLogParam1", methodCall.arguments[5].toString())
- assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[5].toString())
assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED, out)
}
}