diff options
179 files changed, 3621 insertions, 1481 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 0947e33cb4d1..5a3a8d525b69 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.DETECT_SCREEN_CAPTURE; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; +import static android.app.Instrumentation.DEBUG_FINISH_ACTIVITY; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.os.Process.myUid; @@ -7297,6 +7298,9 @@ public class Activity extends ContextThemeWrapper */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void finish(int finishTask) { + if (DEBUG_FINISH_ACTIVITY) { + Log.d("Instrumentation", "finishActivity: finishTask=" + finishTask, new Throwable()); + } if (mParent == null) { int resultCode; Intent resultData; diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index db216b1af974..be270463e576 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -107,6 +107,8 @@ public class Instrumentation { // If set, will print the stack trace for activity starts within the process static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("persist.wm.debug.start_activity", false); + static final boolean DEBUG_FINISH_ACTIVITY = Build.IS_DEBUGGABLE && + SystemProperties.getBoolean("persist.wm.debug.finish_activity", false); /** * @hide diff --git a/core/java/android/app/admin/OWNERS b/core/java/android/app/admin/OWNERS index 308f1d622c25..4f3f5d9c3535 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 e4ffd7f41aa0..b53bdc202a89 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 5953890ad85f..93724bb4949d 100644 --- a/core/java/android/content/ClipDescription.java +++ b/core/java/android/content/ClipDescription.java @@ -136,6 +136,14 @@ public class ClipDescription implements Parcelable { "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/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index ba356bb55194..65148726224e 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -16,6 +16,8 @@ package android.database; +import static java.util.Objects.requireNonNull; + import android.annotation.BytesLong; import android.annotation.IntRange; import android.compat.annotation.UnsupportedAppUsage; @@ -640,6 +642,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { */ public boolean putBlob(byte[] value, @IntRange(from = 0) int row, @IntRange(from = 0) int column) { + requireNonNull(value); acquireReference(); try { return nativePutBlob(mWindowPtr, value, row - mStartPos, column); @@ -658,6 +661,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { */ public boolean putString(String value, @IntRange(from = 0) int row, @IntRange(from = 0) int column) { + requireNonNull(value); acquireReference(); try { return nativePutString(mWindowPtr, value, row - mStartPos, column); diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index cbac912e33e8..ca3e3d2ad61b 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -569,7 +569,6 @@ public class Camera { return native_setup( new WeakReference<>(this), cameraId, - ActivityThread.currentOpPackageName(), rotationOverride, forceSlowJpegMode, clientAttribution.getParcel(), @@ -660,7 +659,6 @@ public class Camera { private native int native_setup( Object cameraThis, int cameraId, - String packageName, int rotationOverride, boolean forceSlowJpegMode, Parcel clientAttributionParcel, diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 2dbd4b81fe6c..6201359ca53e 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -980,6 +980,8 @@ public final class CameraManager { clientAttribution.uid = USE_CALLING_UID; clientAttribution.pid = USE_CALLING_PID; clientAttribution.deviceId = contextAttribution.deviceId; + clientAttribution.packageName = mContext.getOpPackageName(); + clientAttribution.attributionTag = mContext.getAttributionTag(); clientAttribution.next = new AttributionSourceState[0]; return clientAttribution; } @@ -1041,8 +1043,6 @@ public final class CameraManager { cameraService.connectDevice( callbacks, cameraId, - mContext.getOpPackageName(), - mContext.getAttributionTag(), oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion, rotationOverride, diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java index 93c54395f972..4bb66ed26cbc 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 @@ final class NavigationBarConstants { // 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 e28f34528f42..b522e9bdca45 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 @@ public final class NavigationBarView extends FrameLayout { 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/util/SequenceUtils.java b/core/java/android/util/SequenceUtils.java index f833ce314c91..4f8db0fb260b 100644 --- a/core/java/android/util/SequenceUtils.java +++ b/core/java/android/util/SequenceUtils.java @@ -25,8 +25,8 @@ package android.util; * {@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 @@ public final class SequenceUtils { } /** - * 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 47669427cb9d..f77e21935f05 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5512,6 +5512,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 88dc3f4a3f37..07cbaa9c905f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -23,7 +23,7 @@ import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUN 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, } 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, } 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 @@ public final class ViewRootImpl implements ViewParent, 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 f4f6c8aa3636..cd033caf4871 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_ADJACENT_TASK_FRA 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.InsetsFrameProvider; 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 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * 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 final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { .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 @@ public final class WindowContainerTransaction implements Parcelable { mShortcutInfo = copy.mShortcutInfo; mAlwaysOnTop = copy.mAlwaysOnTop; mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch; + mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents; } protected HierarchyOp(Parcel in) { @@ -1620,6 +1654,7 @@ public final class WindowContainerTransaction implements Parcelable { mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR); mAlwaysOnTop = in.readBoolean(); mReparentLeafTaskIfRelaunch = in.readBoolean(); + mIsTrimmableFromRecents = in.readBoolean(); } public int getType() { @@ -1715,6 +1750,12 @@ public final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeTypedObject(mShortcutInfo, flags); dest.writeBoolean(mAlwaysOnTop); dest.writeBoolean(mReparentLeafTaskIfRelaunch); + dest.writeBoolean(mIsTrimmableFromRecents); } @Override @@ -1910,6 +1956,8 @@ public final class WindowContainerTransaction implements Parcelable { private boolean mReparentLeafTaskIfRelaunch; + private boolean mIsTrimmableFromRecents; + Builder(int type) { mType = type; } @@ -2000,6 +2048,11 @@ public final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { 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 7432ca7956df..e54853631d5f 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -141,3 +141,10 @@ flag { 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 621b2c41050e..6b2454568c29 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -70,17 +70,6 @@ flag { } 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/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java index d24487412313..572a599dc8f5 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.File; 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 @@ public class LegacyProtoLogImpl implements IProtoLog { 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 @@ public class LegacyProtoLogImpl implements IProtoLog { 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 @@ public class LegacyProtoLogImpl implements IProtoLog { @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 @@ public class LegacyProtoLogImpl implements IProtoLog { } 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 @@ public class LegacyProtoLogImpl implements IProtoLog { // 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 000000000000..ebdad6d52933 --- /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 42fa6ac0407d..07be7006cd73 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 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.IOException; 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 @@ public class PerfettoProtoLogImpl implements IProtoLog { 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 class PerfettoProtoLogImpl implements IProtoLog { public PerfettoProtoLogImpl( ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, ProtoLogViewerConfigReader viewerConfigReader, - TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater ) { Producer.init(InitArguments.DEFAULTS); @@ -140,7 +150,6 @@ public class PerfettoProtoLogImpl implements IProtoLog { mDataSource.register(params); this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; this.mViewerConfigReader = viewerConfigReader; - this.mLogGroups = logGroups; this.mCacheUpdater = cacheUpdater; } @@ -149,23 +158,70 @@ public class PerfettoProtoLogImpl implements IProtoLog { */ @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; } + 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 { - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + 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 @@ public class PerfettoProtoLogImpl implements IProtoLog { 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); - 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) { + StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE"); if (args != null) { - try { - message = TextUtils.formatSimple(messageString, args); - } catch (Exception ex) { - Slog.w(LOG_TAG, "Invalid ProtoLog format string.", ex); - } - } else { - message = messageString; + 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]; } - if (message == null) { - StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")"); - for (Object o : args) { - builder.append(" ").append(o); + + logToLogcat(tag, level, messageString, args); + } + + 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()) + ")"; } - message = builder.toString(); + } else { + message = messageString; } passToLogcat(tag, level, message); } @@ -320,25 +381,11 @@ public class PerfettoProtoLogImpl implements IProtoLog { } } - 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 @@ public class PerfettoProtoLogImpl implements IProtoLog { // 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 @@ public class PerfettoProtoLogImpl implements IProtoLog { 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 @@ public class PerfettoProtoLogImpl implements IProtoLog { 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 @@ public class PerfettoProtoLogImpl implements IProtoLog { }); } + 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 @@ public class PerfettoProtoLogImpl implements IProtoLog { ProtoLogDataSource.IncrementalState> ctx, Map<String, Integer> internMap, long fieldId, - String string + @NonNull String string ) { final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState(); @@ -523,25 +658,17 @@ public class PerfettoProtoLogImpl implements IProtoLog { @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 @@ public class PerfettoProtoLogImpl implements IProtoLog { } 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 @@ public class PerfettoProtoLogImpl implements IProtoLog { 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 @@ public class PerfettoProtoLogImpl implements IProtoLog { 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 0118c056d682..87678e5922f9 100644 --- a/core/java/com/android/internal/protolog/ProtoLog.java +++ b/core/java/com/android/internal/protolog/ProtoLog.java @@ -44,21 +44,23 @@ public class ProtoLog { // 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 @@ public class ProtoLog { * @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 @@ public class ProtoLog { * @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 @@ public class ProtoLog { * @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 @@ public class ProtoLog { * @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 @@ public class ProtoLog { * @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 @@ public class ProtoLog { * @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 @@ public class ProtoLog { * @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."); + } + + 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(); } - return null; } } diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java index 6ab79b92784e..84f3237142b8 100644 --- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java +++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java @@ -40,6 +40,7 @@ import com.android.internal.protolog.common.LogLevel; 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 class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance, } 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 6d142afce626..3082295a522c 100644 --- a/core/java/com/android/internal/protolog/ProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java @@ -54,48 +54,33 @@ public class ProtoLogImpl { 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 @@ public class ProtoLogImpl { } /** + * 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 f72d9f79958d..f5695acd0614 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 @@ public interface IProtoLog { * @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 @@ public interface IProtoLog { * @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/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index b8fd3d065d8d..3f74fac35bb7 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -582,8 +582,8 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jin // connect to camera service static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jint cameraId, jstring clientPackageName, - jint rotationOverride, jboolean forceSlowJpegMode, + jint cameraId, jint rotationOverride, + jboolean forceSlowJpegMode, jobject jClientAttributionParcel, jint devicePolicy) { AttributionSourceState clientAttribution; @@ -591,16 +591,8 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj return -EACCES; } - // Convert jstring to String16 - const char16_t *rawClientName = reinterpret_cast<const char16_t*>( - env->GetStringChars(clientPackageName, NULL)); - jsize rawClientNameLen = env->GetStringLength(clientPackageName); - std::string clientName = toStdString(rawClientName, rawClientNameLen); - env->ReleaseStringChars(clientPackageName, - reinterpret_cast<const jchar*>(rawClientName)); - int targetSdkVersion = android_get_application_target_sdk_version(); - sp<Camera> camera = Camera::connect(cameraId, clientName, targetSdkVersion, rotationOverride, + sp<Camera> camera = Camera::connect(cameraId, targetSdkVersion, rotationOverride, forceSlowJpegMode, clientAttribution, devicePolicy); if (camera == NULL) { return -EACCES; @@ -1089,7 +1081,7 @@ static const JNINativeMethod camMethods[] = { (void *)android_hardware_Camera_getNumberOfCameras}, {"_getCameraInfo", "(IILandroid/os/Parcel;ILandroid/hardware/Camera$CameraInfo;)V", (void *)android_hardware_Camera_getCameraInfo}, - {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;IZLandroid/os/Parcel;I)I", + {"native_setup", "(Ljava/lang/Object;IIZLandroid/os/Parcel;I)I", (void *)android_hardware_Camera_native_setup}, {"native_release", "()V", (void *)android_hardware_Camera_release}, {"setPreviewSurface", "(Landroid/view/Surface;)V", diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 4f0a836224f9..dc3d9355f148 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 419a615d92ff..d25f59d7c488 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/tests/coretests/src/android/util/SequenceUtilsTest.java b/core/tests/coretests/src/android/util/SequenceUtilsTest.java index 020520dbcf85..6ca1751a675b 100644 --- a/core/tests/coretests/src/android/util/SequenceUtilsTest.java +++ b/core/tests/coretests/src/android/util/SequenceUtilsTest.java @@ -19,7 +19,7 @@ package android.util; 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 @@ public class SequenceUtilsTest { } @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 9337bf67625a..033ac7cec1bc 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 @@ public class ViewRootImplTest { 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/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index 822a07c23950..544f0f38f48c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -894,9 +894,7 @@ class DividerPresenter implements View.OnTouchListener { private static boolean isDraggingToFullscreenAllowed( @NonNull DividerAttributes dividerAttributes) { - // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is - // updated to v7. - return false; + return dividerAttributes.isDraggingToFullscreenAllowed(); } /** diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 1e6824196687..dbcad8aab45b 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -190,6 +190,15 @@ java_library { ], } +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/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 59831689b525..f0d80a02243a 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 @@ -37,10 +37,6 @@ enum class DesktopModeFlags( DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true), WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true); - // Local cache for toggle override, which is initialized once on its first access. It needs to be - // refreshed only on reboots as overridden state takes effect on reboots. - private var cachedToggleOverride: ToggleOverride? = null - /** * Determines state of flag based on the actual flag and desktop mode developer option overrides. * @@ -88,11 +84,12 @@ enum class DesktopModeFlags( // 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()) @@ -101,7 +98,6 @@ enum class DesktopModeFlags( } } - // TODO(b/348193756): Share ToggleOverride enum with Settings 'DesktopModePreferenceController' /** * Override state of desktop mode developer option toggle. * @@ -117,8 +113,6 @@ enum class DesktopModeFlags( 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] @@ -128,23 +122,33 @@ enum class DesktopModeFlags( } } - private fun Int.convertToToggleOverrideWithFallback( - fallbackOverride: ToggleOverride - ): ToggleOverride { - return settingToToggleOverrideMap[this] - ?: run { - Log.w(TAG, "Unknown toggleOverride int $this") - fallbackOverride - } - } - - private companion object { - const val TAG = "DesktopModeFlags" + companion object { + private const val TAG = "DesktopModeFlags" /** * Key for non-persistent System Property which is used to store desktop windowing developer * option overrides. */ - const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" + private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" + + /** + * Local cache for toggle override, which is initialized once on its first access. It needs to + * 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 d41600b140c4..f014e559d4b4 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 @@ public class ShellTaskOrganizer extends TaskOrganizer { } } + /** + * 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 a124f95d7431..c93c11eb2fc2 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.app.Activity 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 @@ class CreateBubbleShortcutActivity : Activity() { 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 ae7940ca1b65..e578e9e76979 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.ComponentName 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 @@ class ShowBubblesActivity : Activity() { // 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 81592c35e4ac..e92b0b59d2ef 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 @@ package com.android.wm.shell.common 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 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { 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 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { * @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 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { * @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 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { } 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 9e8dfb5f0c6f..a6be64070ac1 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.content.pm.PackageManager 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 @@ class MultiInstanceHelper @JvmOverloads constructor( 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 @@ class MultiInstanceHelper @JvmOverloads constructor( // 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 @@ class MultiInstanceHelper @JvmOverloads constructor( 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 3ad60e7031e5..1bc179551825 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 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { 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 bdef4f4b094c..51f9de8305f8 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 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange .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 5d121c23c6e1..46c1a43f9efe 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.LayoutInflater; 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 @@ public final class SplitWindowManager extends WindowlessWindowManager { 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 aa499d9ee8a0..eeceaa943af2 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 @@ public abstract class WMShellModule { 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 fbc11c19a5a2..400882a8da9a 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 @@ class DesktopModeEventLogger { * 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 @@ class DesktopModeEventLogger { * 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 @@ class DesktopModeEventLogger { * 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 @@ class DesktopModeEventLogger { * 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 @@ class DesktopModeEventLogger { * 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 275f725dc054..066b5ad39d0f 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.desktopmode.DesktopModeStatus 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 @@ class DesktopModeLoggerTransitionObserver( ) { // 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 @@ class DesktopModeLoggerTransitionObserver( info.flags == 0 && tasksSavedForRecents.isNotEmpty() ) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Canceled recents animation, restoring tasks" ) @@ -202,7 +201,7 @@ class DesktopModeLoggerTransitionObserver( } } - 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 df79b1575894..ca0586418041 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 android.window.WindowContainerToken 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { } } 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { } } 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 @@ class DesktopModeTaskRepository { // 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 @@ class DesktopModeTaskRepository { /** 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 @@ class DesktopModeTaskRepository { // 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 @@ class DesktopModeTaskRepository { /** 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 @@ class DesktopModeTaskRepository { /** 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 @@ class DesktopModeTaskRepository { /** 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 @@ class DesktopModeTaskRepository { ) 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 985901d5ac75..18157d6255e3 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 android.window.WindowContainerTransaction 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.ShellInit 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 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( /** 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( transitionSource: DesktopModeTransitionSource, ): Boolean { recentTasksController?.findTaskInBackground(taskId)?.let { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d", taskId @@ -346,14 +346,14 @@ class DesktopTasksController( ) { 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 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, dragToDesktopValueAnimator: MoveToDesktopAnimator, ) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: startDragToDesktop taskId=%d", taskInfo.taskId @@ -396,7 +396,7 @@ class DesktopTasksController( * [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 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( /** 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 @@ class DesktopTasksController( * [startDragToDesktop]. */ fun cancelDragToDesktop(task: RunningTaskInfo) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: cancelDragToDesktop taskId=%d", task.taskId @@ -512,7 +512,7 @@ class DesktopTasksController( position: Point, transitionSource: DesktopModeTransitionSource ) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToFullscreen with animation taskId=%d", task.taskId @@ -540,7 +540,7 @@ class DesktopTasksController( /** 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( * 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 @@ class DesktopTasksController( ) 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 @@ class DesktopTasksController( wct: WindowContainerTransaction, newTaskIdInFront: Int? = null ): RunningTaskInfo? { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s", newTaskIdInFront ?: "null" @@ -815,7 +815,7 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( transition: IBinder, request: TransitionRequestInfo ): WindowContainerTransaction? { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleRequest request=%s", request @@ -915,7 +915,7 @@ class DesktopTasksController( } if (!shouldHandleRequest) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skipping handleRequest reason=%s", reason @@ -939,7 +939,7 @@ class DesktopTasksController( } } } - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleRequest result=%s", result ?: "null" @@ -977,15 +977,15 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( } 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 3f5bd1a4f5c7..534cc22ada47 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.view.WindowManager.TRANSIT_TO_BACK 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 @@ class DesktopTasksLimiter ( 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 @@ class DesktopTasksLimiter ( } 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 @@ class DesktopTasksLimiter ( 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 @@ class DesktopTasksLimiter ( * 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 @@ class DesktopTasksLimiter ( wct: WindowContainerTransaction, newFrontTaskInfo: RunningTaskInfo, ): RunningTaskInfo? { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d", newFrontTaskInfo.taskId) @@ -217,7 +217,7 @@ class DesktopTasksLimiter ( 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 @@ class DesktopTasksLimiter ( 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 f01f6450ae30..246fd9281975 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.os.IBinder 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 @@ class DesktopTasksTransitionObserver( 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 c4a4474689fa..1c2415c236ad 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.app.ActivityManager 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 @@ import com.android.wm.shell.util.KtProtoLog 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 d99b724c936f..ddee8fac8f44 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.TransitionInfo.Change 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_CANCEL_D 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( ) { 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/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md index b1cbe8d98397..3572d161f5b9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md @@ -97,7 +97,7 @@ adb reboot adb logcat -s "SurfaceControlRegistry" ``` -## Tracing activity starts in the app process +## Tracing activity starts & finishes in the app process It's sometimes useful to know when to see a stack trace of when an activity starts in the app code (ie. if you are repro'ing a bug related to activity starts). You can enable this system property to @@ -113,6 +113,19 @@ adb shell setprop persist.wm.debug.start_activity \"\" adb reboot ``` +Likewise, to trace where a finish() call may be made in the app process, you can enable this system +property: +```shell +# Enabling +adb shell setprop persist.wm.debug.finish_activity true +adb reboot +adb logcat -s "Instrumentation" + +# Disabling +adb shell setprop persist.wm.debug.finish_activity \"\" +adb reboot +``` + ## Dumps Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a 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 b3c3a3dcf272..e0b08668b1d4 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.View; 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 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll 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 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll 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 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll 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 9c7476d1a1b0..95fe8b6f1f4e 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.RemoteException; 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 @@ public class DragAndDropPolicy { 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 @@ public class DragAndDropPolicy { ? 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 @@ public class DragAndDropPolicy { 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 @@ public class DragAndDropPolicy { } } 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 @@ public class DragAndDropPolicy { 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 @@ public class DragAndDropPolicy { } @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 @@ public class DragAndDropPolicy { @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 910175ef3023..f0514e3e49f5 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.ViewTreeObserver; 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 @@ public class DragLayout extends LinearLayout /** * 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 @@ public class DragLayout extends LinearLayout 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 3bedef21184e..dcbdfa349687 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 @@ package com.android.wm.shell.draganddrop; 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.ClipData; 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 @@ public class DragSession { @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 @@ public class DragSession { 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 @@ public class DragSession { * 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 fbb4bc42a6b8..9539a456502f 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.Slog; 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 @@ public class RecentTasksController implements TaskStackListenerCallback, } /** - * 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 @@ public class RecentTasksController implements TaskStackListenerCallback, 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 b4941a5b49b5..e659151fee7f 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.SurfaceSession; 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, * 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } } - 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @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 class SplitScreenController implements DragAndDropPolicy.Starter, 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 d9e97766e4fe..41042344fd3a 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - /** 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 3a680097554f..dd4595a70211 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 androidx.annotation.VisibleForTesting; 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 @@ public class ShellInit { */ @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 ee6c81a3fa04..000000000000 --- 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/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 c725b08d2f5e..430f80b9a927 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.AssertionInvocationGroup 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 @@ class DesktopModeFlickerScenarios { 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 @@ class DesktopModeFlickerScenarios { 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 @@ class DesktopModeFlickerScenarios { 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 3f2603aec86a..35b2f56bca92 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp @@ -107,7 +107,7 @@ android_test { 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 @@ android_test { 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 636c6326d213..f5847cc27071 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 @@ public class DividerViewTest extends ShellTestCase { 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 582fb91559e5..97fa8d6ceca9 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 @@ public class DragAndDropPolicyTest extends ShellTestCase { 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 @@ public class DragAndDropPolicyTest extends ShellTestCase { 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 @@ public class DragAndDropPolicyTest extends ShellTestCase { 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 4661042fcb6d..b1d62f485a2a 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_MODE 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 @@ class DesktopModeFlagsTest : ShellTestCase() { @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 @@ class DesktopModeFlagsTest : ShellTestCase() { @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 @@ class DesktopModeFlagsTest : ShellTestCase() { @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 @@ class DesktopModeFlagsTest : ShellTestCase() { @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 @@ class DesktopModeFlagsTest : ShellTestCase() { @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 @@ class DesktopModeFlagsTest : ShellTestCase() { @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 @@ class DesktopModeFlagsTest : ShellTestCase() { @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 @@ class DesktopModeFlagsTest : ShellTestCase() { @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 @@ class DesktopModeFlagsTest : ShellTestCase() { @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 @@ class DesktopModeFlagsTest : ShellTestCase() { 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 @@ -427,15 +441,10 @@ class DesktopModeFlagsTest : ShellTestCase() { } private fun resetCache() { - val cachedToggleOverrideDesktopMode = - DESKTOP_WINDOWING_MODE::class.java.getDeclaredField("cachedToggleOverride") - cachedToggleOverrideDesktopMode.isAccessible = true - cachedToggleOverrideDesktopMode.set(DESKTOP_WINDOWING_MODE, null) - - val cachedToggleOverrideWallpaperActivity = - WALLPAPER_ACTIVITY::class.java.getDeclaredField("cachedToggleOverride") - cachedToggleOverrideWallpaperActivity.isAccessible = true - cachedToggleOverrideWallpaperActivity.set(WALLPAPER_ACTIVITY, null) + val cachedToggleOverride = + DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride") + cachedToggleOverride.isAccessible = true + cachedToggleOverride.set(null, null) // Clear override cache stored in System property System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) 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 3c387f0d7c34..5b95b1588814 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.isA; 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.ComponentName; 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 @@ public class SplitScreenControllerTests extends ShellTestCase { 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 @@ public class SplitScreenControllerTests extends ShellTestCase { 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 @@ public class SplitScreenControllerTests extends ShellTestCase { 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 @@ public class SplitScreenControllerTests extends ShellTestCase { @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 @@ public class SplitScreenControllerTests extends ShellTestCase { 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 @@ public class SplitScreenControllerTests extends ShellTestCase { 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 @@ public class SplitScreenControllerTests extends ShellTestCase { 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/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 316cefa1eb99..b1803e97b107 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -360,7 +360,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { isTopActivityStyleFloating = true numActivities = 1 } - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) } setUpMockDecorationsForTasks(task) onTaskOpening(task) diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java index 102d21acfd19..43acbb1eacc8 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java @@ -2246,6 +2246,8 @@ public class CameraTestUtils extends Assert { clientAttribution.uid = -1; // USE_CALLING_UID clientAttribution.pid = -1; // USE_CALLING_PID clientAttribution.deviceId = contextAttribution.deviceId; + clientAttribution.packageName = context.getOpPackageName(); + clientAttribution.attributionTag = context.getAttributionTag(); clientAttribution.next = new AttributionSourceState[0]; return clientAttribution; } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java index ad3374a7da6a..ac85ab7f6a6e 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java @@ -169,10 +169,8 @@ public class CameraBinderTest extends AndroidTestCase { ICameraClient dummyCallbacks = new DummyCameraClient(); - String clientPackageName = getContext().getPackageName(); - ICamera cameraUser = mUtils.getCameraService() - .connect(dummyCallbacks, cameraId, clientPackageName, + .connect(dummyCallbacks, cameraId, getContext().getApplicationInfo().targetSdkVersion, ICameraService.ROTATION_OVERRIDE_NONE, /*forceSlowJpegMode*/false, @@ -267,8 +265,6 @@ public class CameraBinderTest extends AndroidTestCase { ICameraDeviceCallbacks dummyCallbacks = new DummyCameraDeviceCallbacks(); - String clientPackageName = getContext().getPackageName(); - String clientAttributionTag = getContext().getAttributionTag(); AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext); clientAttribution.deviceId = DEVICE_ID_DEFAULT; @@ -277,7 +273,6 @@ public class CameraBinderTest extends AndroidTestCase { ICameraDeviceUser cameraUser = mUtils.getCameraService().connectDevice( dummyCallbacks, String.valueOf(cameraId), - clientPackageName, clientAttributionTag, 0 /*oomScoreOffset*/, getContext().getApplicationInfo().targetSdkVersion, ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java index 0ab1ee9095e0..35ad924cee74 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -242,9 +242,6 @@ public class CameraDeviceBinderTest extends AndroidTestCase { ICameraDeviceCallbacks.Stub dummyCallbacks = new DummyCameraDeviceCallbacks(); - String clientPackageName = getContext().getPackageName(); - String clientAttributionTag = getContext().getAttributionTag(); - mMockCb = spy(dummyCallbacks); AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext); @@ -252,7 +249,6 @@ public class CameraDeviceBinderTest extends AndroidTestCase { clientAttribution.uid = ICameraService.USE_CALLING_UID; mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId, - clientPackageName, clientAttributionTag, /*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion, ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, DEVICE_POLICY_DEFAULT); assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser); diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp index 483403971864..b56b944955d7 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp @@ -20,6 +20,7 @@ android_library { 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 8b27626a08e4..465905170347 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 @@ public class CollapsingToolbarAppCompatActivity extends AppCompatActivity { @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 86ce2ab6109f..3965303d3ba5 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 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { @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 000000000000..6e53012e1245 --- /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 6052be3b52e2..b6e80c784f10 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 c9934adfad22..fb23637a9f4c 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 @@ internal class RestrictedSwitchPreferenceModel( 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 38c871c698c9..403e219f3467 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -102,3 +102,13 @@ flag { 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 67139b510d85..f89fe935df38 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 8bdbee38ab85..9be3159716b8 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.KeyValueListParser; 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 @@ public class BatterySaverUtils { 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 @@ public class BatterySaverUtils { 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 @@ public class BatterySaverUtils { * @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 @@ public class BatterySaverUtils { 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 ef0f6cbc6ed9..6c2bd412cfbd 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 @@ public class SignalDrawable extends DrawableWrapper { 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/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java index 80301c02dd6a..b143b22c511b 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 com.google.common.truth.Truth.assertThat; 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 @@ public class BatterySaverUtilsTest { private static final int BATTERY_SAVER_THRESHOLD_1 = 15; private static final int BATTERY_SAVER_THRESHOLD_2 = 20; - @Mock - private Context mMockContext; - - @Mock - private ContentResolver mMockResolver; + @Rule(order = 0) public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule(order = 1) public final MockitoRule mMockitoRule = MockitoJUnit.rule(); - @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 @@ public class BatterySaverUtilsTest { } @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 @@ public class BatterySaverUtilsTest { } @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 @@ public class BatterySaverUtilsTest { } @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 @@ public class BatterySaverUtilsTest { } @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 @@ public class BatterySaverUtilsTest { } @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 @@ public class BatterySaverUtilsTest { } @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 @@ public class BatterySaverUtilsTest { } @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 @@ public class BatterySaverUtilsTest { KEY_NO_SCHEDULE); } + @EnableFlags(Flags.FLAG_EXTREME_POWER_LOW_STATE_VULNERABILITY) + @Test + 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 testSetBatterySaverScheduleMode_setSchedule() { + 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/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 54f0d7a4d853..e8ef620d6c0c 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.isSystemSettingsKey; 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 @@ public class SettingsProvider extends ContentProvider { } } + 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 861c405b1542..452edd924a26 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -740,8 +740,6 @@ final class SettingsState { // The settings provider must hold its lock when calling here. @GuardedBy("mLock") public void removeSettingsForPackageLocked(String packageName) { - boolean removedSomething = false; - final int settingCount = mSettings.size(); for (int i = settingCount - 1; i >= 0; i--) { String name = mSettings.keyAt(i); @@ -752,14 +750,9 @@ final class SettingsState { } Setting setting = mSettings.valueAt(i); if (packageName.equals(setting.packageName)) { - mSettings.removeAt(i); - removedSomething = true; + deleteSettingLocked(setting.name); } } - - if (removedSomething) { - scheduleWriteIfNeededLocked(); - } } // The settings provider must hold its lock when calling here. @@ -781,6 +774,13 @@ final class SettingsState { } } + @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 d20fbf591a25..4f5955b1c6ca 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 @@ flag { 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/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 4b4ced3c0753..48ce49dbfb3a 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -988,7 +988,40 @@ public class SettingsStateTest { } @Test - public void testGetFlagOverrideToSync() { + public void testMemoryUsagePerPackage_StatsUpdatedOnAppDataCleared() { + SettingsState settingsState = + new SettingsState( + InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper()); + final String testKey1 = SETTING_NAME; + final String testKey2 = SETTING_NAME + "_2"; + final String testValue1 = Strings.repeat("A", 9000); + final String testValue2 = Strings.repeat("A", 9001); + final String packageName = "p"; + // Inserting the first setting should be okay + settingsState.insertSettingLocked(testKey1, testValue1, null, true, packageName); + int expectedMemUsageForPackage = (testKey1.length() + testValue1.length() + + testValue1.length() /* size for default */) * Character.BYTES; + assertEquals(expectedMemUsageForPackage, settingsState.getMemoryUsage(packageName)); + // Inserting the second setting should fail + try { + settingsState.insertSettingLocked(testKey2, testValue2, null, true, packageName); + fail("Should throw because it exceeded max memory usage per package"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().startsWith("You are adding too many system settings.")); + } + // Now clear app data and check that the memory usage is cleared + settingsState.removeSettingsForPackageLocked(packageName); + assertEquals(0, settingsState.getMemoryUsage(packageName)); + // Try inserting the second setting again and it should go through + settingsState.insertSettingLocked(testKey2, testValue2, null, true, packageName); + expectedMemUsageForPackage = (testKey2.length() + testValue2.length() + + testValue2.length() /* size for default */) * Character.BYTES; + assertEquals(expectedMemUsageForPackage, settingsState.getMemoryUsage(packageName)); + } + + @Test + public void testGetFlagOverrideToSync() { int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); Object lock = new Object(); SettingsState settingsState = diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 469b9cef0411..9cbb1bd9c71c 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -555,6 +555,7 @@ android_library { "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 @@ android_library { "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 3f165a3bcb63..a1f1a08d4bf2 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -824,13 +824,6 @@ flag { } 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/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt index 887e3494b49e..25e91bed5808 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.composable +import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -48,6 +49,16 @@ class LockscreenContent( fun SceneScope.Content( modifier: Modifier = Modifier, ) { + val isContentVisible: Boolean by viewModel.isContentVisible.collectAsStateWithLifecycle() + if (!isContentVisible) { + // If the content isn't supposed to be visible, show a large empty box as it's needed + // for scene transition animations (can't just skip rendering everything or shared + // elements won't have correct final/initial bounds from animating in and out of the + // lockscreen scene). + Box(modifier) + return + } + val coroutineScope = rememberCoroutineScope() val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle() val view = LocalView.current diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java deleted file mode 100644 index 07d889066438..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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.ambient.touch; - - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.DreamManager; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; -import android.view.GestureDetector; -import android.view.MotionEvent; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.Flags; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.shade.ShadeViewController; -import com.android.systemui.shared.system.InputChannelCompat; -import com.android.systemui.statusbar.phone.CentralSurfaces; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ShadeTouchHandlerTest extends SysuiTestCase { - @Mock - CentralSurfaces mCentralSurfaces; - - @Mock - ShadeViewController mShadeViewController; - - @Mock - DreamManager mDreamManager; - - @Mock - TouchHandler.TouchSession mTouchSession; - - ShadeTouchHandler mTouchHandler; - - @Captor - ArgumentCaptor<GestureDetector.OnGestureListener> mGestureListenerCaptor; - @Captor - ArgumentCaptor<InputChannelCompat.InputEventListener> mInputListenerCaptor; - - private static final int TOUCH_HEIGHT = 20; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController, - mDreamManager, TOUCH_HEIGHT); - } - - // Verifies that a swipe down in the gesture region is captured by the shade touch handler. - @Test - public void testSwipeDown_captured() { - final boolean captured = swipe(Direction.DOWN); - - assertThat(captured).isTrue(); - } - - // Verifies that a swipe in the upward direction is not captured. - @Test - public void testSwipeUp_notCaptured() { - final boolean captured = swipe(Direction.UP); - - // Motion events not captured as the swipe is going in the wrong direction. - assertThat(captured).isFalse(); - } - - // Verifies that a swipe down forwards captured touches to central surfaces for handling. - @Test - @EnableFlags(Flags.FLAG_COMMUNAL_HUB) - public void testSwipeDown_communalEnabled_sentToCentralSurfaces() { - swipe(Direction.DOWN); - - // Both motion events are sent for central surfaces to process. - verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any()); - } - - // Verifies that a swipe down forwards captured touches to the shade view for handling. - @Test - @DisableFlags(Flags.FLAG_COMMUNAL_HUB) - public void testSwipeDown_communalDisabled_sentToShadeView() { - swipe(Direction.DOWN); - - // Both motion events are sent for the shade view to process. - verify(mShadeViewController, times(2)).handleExternalTouch(any()); - } - - // Verifies that a swipe down while dreaming forwards captured touches to the shade view for - // handling. - @Test - public void testSwipeDown_dreaming_sentToShadeView() { - when(mDreamManager.isDreaming()).thenReturn(true); - - swipe(Direction.DOWN); - - // Both motion events are sent for the shade view to process. - verify(mShadeViewController, times(2)).handleExternalTouch(any()); - } - - // Verifies that a swipe up is not forwarded to central surfaces. - @Test - @EnableFlags(Flags.FLAG_COMMUNAL_HUB) - public void testSwipeUp_communalEnabled_touchesNotSent() { - swipe(Direction.UP); - - // Motion events are not sent for central surfaces to process as the swipe is going in the - // wrong direction. - verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any()); - } - - // Verifies that a swipe up is not forwarded to the shade view. - @Test - @DisableFlags(Flags.FLAG_COMMUNAL_HUB) - public void testSwipeUp_communalDisabled_touchesNotSent() { - swipe(Direction.UP); - - // Motion events are not sent for the shade view to process as the swipe is going in the - // wrong direction. - verify(mShadeViewController, never()).handleExternalTouch(any()); - } - - /** - * Simulates a swipe in the given direction and returns true if the touch was intercepted by the - * touch handler's gesture listener. - * <p> - * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge - * of the gesture region, {@link #TOUCH_HEIGHT}, and goes upward to 0. - */ - private boolean swipe(Direction direction) { - Mockito.clearInvocations(mTouchSession); - mTouchHandler.onSessionStart(mTouchSession); - - verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture()); - verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture()); - - final float startY = direction == Direction.UP ? TOUCH_HEIGHT : 0; - final float endY = direction == Direction.UP ? 0 : TOUCH_HEIGHT; - - // Send touches to the input and gesture listener. - final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, startY, 0); - final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, endY, 0); - mInputListenerCaptor.getValue().onInputEvent(event1); - mInputListenerCaptor.getValue().onInputEvent(event2); - final boolean captured = mGestureListenerCaptor.getValue().onScroll(event1, event2, 0, - startY - endY); - - return captured; - } - - private enum Direction { - DOWN, UP, - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt new file mode 100644 index 000000000000..4314676436ba --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt @@ -0,0 +1,169 @@ +/* + * 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.ambient.touch + +import android.app.DreamManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.view.GestureDetector +import android.view.MotionEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.ambient.touch.TouchHandler.TouchSession +import com.android.systemui.shade.ShadeViewController +import com.android.systemui.shared.system.InputChannelCompat +import com.android.systemui.statusbar.phone.CentralSurfaces +import com.google.common.truth.Truth +import java.util.Optional +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +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 +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ShadeTouchHandlerTest : SysuiTestCase() { + private var mCentralSurfaces = mock<CentralSurfaces>() + private var mShadeViewController = mock<ShadeViewController>() + private var mDreamManager = mock<DreamManager>() + private var mTouchSession = mock<TouchSession>() + + private lateinit var mTouchHandler: ShadeTouchHandler + + private var mGestureListenerCaptor = argumentCaptor<GestureDetector.OnGestureListener>() + private var mInputListenerCaptor = argumentCaptor<InputChannelCompat.InputEventListener>() + + @Before + fun setup() { + mTouchHandler = + ShadeTouchHandler( + Optional.of(mCentralSurfaces), + mShadeViewController, + mDreamManager, + TOUCH_HEIGHT + ) + } + + // Verifies that a swipe down in the gesture region is captured by the shade touch handler. + @Test + fun testSwipeDown_captured() { + val captured = swipe(Direction.DOWN) + Truth.assertThat(captured).isTrue() + } + + // Verifies that a swipe in the upward direction is not captured. + @Test + fun testSwipeUp_notCaptured() { + val captured = swipe(Direction.UP) + + // Motion events not captured as the swipe is going in the wrong direction. + Truth.assertThat(captured).isFalse() + } + + // Verifies that a swipe down forwards captured touches to central surfaces for handling. + @Test + @EnableFlags(Flags.FLAG_COMMUNAL_HUB) + fun testSwipeDown_communalEnabled_sentToCentralSurfaces() { + swipe(Direction.DOWN) + + // Both motion events are sent for central surfaces to process. + verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any()) + } + + // Verifies that a swipe down forwards captured touches to the shade view for handling. + @Test + @DisableFlags(Flags.FLAG_COMMUNAL_HUB) + fun testSwipeDown_communalDisabled_sentToShadeView() { + swipe(Direction.DOWN) + + // Both motion events are sent for the shade view to process. + verify(mShadeViewController, times(2)).handleExternalTouch(any()) + } + + // Verifies that a swipe down while dreaming forwards captured touches to the shade view for + // handling. + @Test + fun testSwipeDown_dreaming_sentToShadeView() { + whenever(mDreamManager.isDreaming).thenReturn(true) + swipe(Direction.DOWN) + + // Both motion events are sent for the shade view to process. + verify(mShadeViewController, times(2)).handleExternalTouch(any()) + } + + // Verifies that a swipe up is not forwarded to central surfaces. + @Test + @EnableFlags(Flags.FLAG_COMMUNAL_HUB) + fun testSwipeUp_communalEnabled_touchesNotSent() { + swipe(Direction.UP) + + // Motion events are not sent for central surfaces to process as the swipe is going in the + // wrong direction. + verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any()) + } + + // Verifies that a swipe up is not forwarded to the shade view. + @Test + @DisableFlags(Flags.FLAG_COMMUNAL_HUB) + fun testSwipeUp_communalDisabled_touchesNotSent() { + swipe(Direction.UP) + + // Motion events are not sent for the shade view to process as the swipe is going in the + // wrong direction. + verify(mShadeViewController, never()).handleExternalTouch(any()) + } + + /** + * Simulates a swipe in the given direction and returns true if the touch was intercepted by the + * touch handler's gesture listener. + * + * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge + * of the gesture region, [.TOUCH_HEIGHT], and goes upward to 0. + */ + private fun swipe(direction: Direction): Boolean { + clearInvocations(mTouchSession) + mTouchHandler.onSessionStart(mTouchSession) + verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture()) + verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture()) + val startY = (if (direction == Direction.UP) TOUCH_HEIGHT else 0).toFloat() + val endY = (if (direction == Direction.UP) 0 else TOUCH_HEIGHT).toFloat() + + // Send touches to the input and gesture listener. + val event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0f, startY, 0) + val event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0f, endY, 0) + mInputListenerCaptor.lastValue.onInputEvent(event1) + mInputListenerCaptor.lastValue.onInputEvent(event2) + return mGestureListenerCaptor.lastValue.onScroll(event1, event2, 0f, startY - endY) + } + + private enum class Direction { + DOWN, + UP + } + + companion object { + private const val TOUCH_HEIGHT = 20 + } +} 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 000000000000..4a5342ac6ae1 --- /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/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt index de4b999b3899..875e9e0210fb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.viewmodel import android.platform.test.flag.junit.FlagsParameterization @@ -27,10 +29,13 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos @@ -38,6 +43,8 @@ import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.Locale +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -201,6 +208,44 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa } } + @Test + fun isContentVisible_whenNotOccluded_visible() = + with(kosmos) { + testScope.runTest { + val isContentVisible by collectLastValue(underTest.isContentVisible) + + keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, null) + runCurrent() + assertThat(isContentVisible).isTrue() + } + } + + @Test + fun isContentVisible_whenOccluded_notVisible() = + with(kosmos) { + testScope.runTest { + val isContentVisible by collectLastValue(underTest.isContentVisible) + + keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null) + runCurrent() + assertThat(isContentVisible).isFalse() + } + } + + @Test + fun isContentVisible_whenOccluded_notVisible_evenIfShadeShown() = + with(kosmos) { + testScope.runTest { + val isContentVisible by collectLastValue(underTest.isContentVisible) + keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null) + runCurrent() + + sceneInteractor.snapToScene(Scenes.Shade, "") + runCurrent() + assertThat(isContentVisible).isFalse() + } + } + private fun prepareConfiguration(): Int { val configuration = context.resources.configuration configuration.setLayoutDirection(Locale.US) 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 fd1b21332973..b5e47d167fa3 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.ExperimentalCoroutinesApi 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 @@ class SceneContainerStartableTest : SysuiTestCase() { 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 8b4265f552fe..5ef3485a8e51 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.ext.junit.runners.AndroidJUnit4 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 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { } @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/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 0350cd7dab98..ca55c2394203 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 80b9ec7748b9..84d5dcbae253 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 0637d3911b29..0bc2c82538c8 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1235,6 +1235,10 @@ <string name="accessibility_action_label_remove_widget">remove widget</string> <!-- Label for accessibility action to place a widget in edit mode after selecting move widget. [CHAR LIMIT=NONE] --> <string name="accessibility_action_label_place_widget">place selected widget</string> + <!-- Title in the communal widget picker. [CHAR LIMIT=50] --> + <string name="communal_widget_picker_title">Lock screen widgets</string> + <!-- Text displayed below the title in the communal widget picker providing additional details about the communal surface. [CHAR LIMIT=80] --> + <string name="communal_widget_picker_description">Anyone can view widgets on your lock screen, even if your tablet\'s locked.</string> <!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] --> <string name="communal_widgets_disclaimer_title">Lock screen widgets</string> <!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 7475eb2eceaa..047578c43159 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 000000000000..9a5c77ac1679 --- /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/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index 2e92438ab890..4f54fee3f498 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 @@ -187,6 +187,11 @@ constructor( CommunalWidgetCategories.defaultCategories ) putExtra(EXTRA_UI_SURFACE_KEY, EXTRA_UI_SURFACE_VALUE) + putExtra(EXTRA_PICKER_TITLE, resources.getString(R.string.communal_widget_picker_title)) + putExtra( + EXTRA_PICKER_DESCRIPTION, + resources.getString(R.string.communal_widget_picker_description) + ) putParcelableArrayListExtra(EXTRA_ADDED_APP_WIDGETS_KEY, excludeList) } } @@ -214,6 +219,8 @@ constructor( private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width" private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height" + private const val EXTRA_PICKER_TITLE = "picker_title" + 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" 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 572283ab839f..08cfd37fea63 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.display.DisplayModule; 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; @@ -259,7 +260,8 @@ import javax.inject.Named; UserModule.class, UtilModule.class, NoteTaskModule.class, - WalletModule.class + WalletModule.class, + ContextualEducationModule.class }, subcomponents = { ComplicationComponent.class, 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 000000000000..e2bcb6bc2457 --- /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 000000000000..af35e8c3662b --- /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 000000000000..c9dd833dac75 --- /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 000000000000..229511a20caf --- /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 000000000000..3b161b659af9 --- /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 91d528074723..817849c41297 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 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.flowOf 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 @@ interface KeyboardRepository { 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 6364a6fb53c9..1f0aef8ab977 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 android.app.Activity 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 @@ interface ShortcutHelperModule { @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 133dab6b4d7b..7b0c25e8379d 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 android.view.KeyboardShortcutInfo 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 @@ constructor( @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 @@ constructor( toShortcutCategory( it.keyCharacterMap, SYSTEM, - systemShortcutsSource.shortcutGroups(it.id) + systemShortcutsSource.shortcutGroups(it.id), + keepIcons = true, ) } else { null @@ -85,7 +91,22 @@ constructor( 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 @@ constructor( toShortcutCategory( it.keyCharacterMap, IME, - inputShortcutsSource.shortcutGroups(it.id) + inputShortcutsSource.shortcutGroups(it.id), + keepIcons = false, ) } else { null @@ -109,13 +131,14 @@ constructor( 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 @@ constructor( 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 000000000000..d7cb7db0f15b --- /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 ead10e5be4e1..d41d21a3b4fb 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 com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory 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 @@ constructor( 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 @@ constructor( .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 000000000000..b105ff67beaa --- /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 adc6d952f2c3..5f8570cba7a6 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 5d0535905540..63e167a376d1 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 @@ package com.android.systemui.keyboard.shortcut.shared.model 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 000000000000..8b720a5a44eb --- /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 b703892c2fa7..3b037bc17564 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.shape.RoundedCornerShape 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.Shape 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.dp 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 @@ private val ShortcutCategory.icon: ImageVector 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 @@ private val ShortcutCategory.labelResId: Int 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 @@ private fun SubCategoryTitle(title: String) { @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 @@ private fun ShortcutViewDualPane(shortcut: Shortcut) { } } +@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/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index b32d0950e0d2..d1a84632e0eb 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 @@ const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f * 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( return@postDelayed } - if ((openingWallpaperTargets?.isNotEmpty() == true)) { + if (openingWallpaperTargets?.isNotEmpty() == true) { fadeInWallpaper() - if (fasterUnlockTransition()) fadeOutWallpaper() hideKeyguardViewAfterRemoteAnimation() } else { keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation( @@ -1038,6 +1038,7 @@ class KeyguardUnlockAnimationController @Inject constructor( 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/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 1ea5d1c00561..fe81b20c5367 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt index 1de0abeb931b..8a29f96d3fcd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt @@ -25,6 +25,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteract import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor @@ -48,6 +49,7 @@ constructor( val shadeInteractor: ShadeInteractor, @Application private val applicationScope: CoroutineScope, unfoldTransitionInteractor: UnfoldTransitionInteractor, + occlusionInteractor: SceneContainerOcclusionInteractor, ) { @VisibleForTesting val clockSize = clockInteractor.clockSize @@ -93,6 +95,16 @@ constructor( initialValue = UnfoldTranslations(), ) + /** Whether the content of the scene UI should be shown. */ + val isContentVisible: StateFlow<Boolean> = + occlusionInteractor.isOccludingActivityShown + .map { !it } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = true, + ) + fun getSmartSpacePaddingTop(resources: Resources): Int { return if (clockSize.value == ClockSize.LARGE) { resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) + diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt index 4a4c73ba9c81..98a61df4ca55 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 @@ constructor( 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 b077349ade74..761290071177 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.recordissue.RecordIssueModule.Companion.TILE_SPEC 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 @@ constructor( 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 @@ constructor( 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/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt index 51744aa47c1a..903d662c69ff 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.SysUISingleton 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" @@ -93,9 +93,9 @@ class TraceurMessageSender @Inject constructor(@Background private val backgroun } @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) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt index 233e9b5bf818..2d510e1cb659 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt @@ -43,8 +43,14 @@ constructor( sceneInteractor: SceneInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { - /** Whether a show-when-locked activity is at the top of the current activity stack. */ - private val isOccludingActivityShown: StateFlow<Boolean> = + /** + * Whether a show-when-locked activity is at the top of the current activity stack. + * + * Note: this isn't enough to figure out whether the scene container UI should be invisible as + * that also depends on the things like the state of AOD and the current scene. If the code + * needs that, [invisibleDueToOcclusion] should be collected instead. + */ + val isOccludingActivityShown: StateFlow<Boolean> = keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), @@ -69,6 +75,9 @@ constructor( /** * Whether the scene container should become invisible due to "occlusion" by an in-foreground * "show when locked" activity. + * + * Note: this returns `false` when an overlaid scene (like shade or QS) is shown above the + * occluding activity. */ val invisibleDueToOcclusion: StateFlow<Boolean> = combine( 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 cdab108c0254..eebbb13005b9 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.data.repository.HeadsUpRowRep 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 @@ constructor( 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/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 534d9d2cc824..900201f54bd7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -186,6 +186,7 @@ constructor( interactor.configurationBasedDimensions .map { when { + !it.useSplitShade -> 0 it.useLargeScreenHeader -> it.marginTopLargeScreen else -> it.marginTop } 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 398c1d43d4fc..bd0097e8fc3f 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 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, 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 84e601848b91..d0a62e77539f 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 @@ public class KeyguardStatusBarView extends RelativeLayout { 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 @@ public class KeyguardStatusBarView extends RelativeLayout { 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 231a8c65a246..824415eaea05 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 @@ data class LetterboxAppearance( ) { 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 @@ constructor( 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 @@ constructor( lastAppearanceRegions = originalAppearanceRegions lastLetterboxes = letterboxes return getLetterboxAppearanceInternal( - letterboxes, originalAppearance, originalAppearanceRegions, statusBarBounds) + letterboxes, + originalAppearance, + originalAppearanceRegions, + statusBarBounds + ) .also { lastLetterboxAppearance = it } } @@ -138,7 +144,9 @@ constructor( // 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 @@ constructor( ): LetterboxAppearance { return LetterboxAppearance( originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS, - originalAppearanceRegions) + originalAppearanceRegions + ) } @Appearance @@ -215,7 +224,9 @@ constructor( 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 0316b0eead25..96127b633f70 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.domain.interactor.PrimaryBouncerInteractor; 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor, JavaAdapter javaAdapter, Lazy<SceneInteractor> sceneInteractorLazy, - StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor + StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor, + Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy ) { mContext = context; mViewMediatorCallback = callback; @@ -430,6 +433,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mJavaAdapter = javaAdapter; mSceneInteractorLazy = sceneInteractorLazy; mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor; + mDeviceEntryInteractorLazy = deviceEntryInteractorLazy; } KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -735,6 +739,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * {@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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 97f5efcf4c5f..677d1fdaa01d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -1799,8 +1799,10 @@ internal data class TestCase( when { fingerprint != null && face != null -> "coex" fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps" - fingerprint != null && !fingerprint.isAnySidefpsType -> - "fingerprint only, non-sideFps" + fingerprint != null && fingerprint.isAnyUdfpsType -> "fingerprint only, udfps" + fingerprint != null && + fingerprint.sensorType == FingerprintSensorProperties.TYPE_REAR -> + "fingerprint only, rearFps" face != null -> "face only" else -> "?" } 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 53bcf865b829..361e768a5b51 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 @@ package com.android.systemui.keyboard.data.repository 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.SysuiTestCase 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 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@TestableLooper.RunWithLooper @RunWith(AndroidJUnit4::class) class KeyboardRepositoryTest : SysuiTestCase() { @@ -63,6 +67,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { 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 @@ class KeyboardRepositoryTest : SysuiTestCase() { } 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 @@ class KeyboardRepositoryTest : SysuiTestCase() { 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 000000000000..e49e2b4990b4 --- /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 a5f1f8c0896a..4c1e8696cdc1 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.ShortcutCategory 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 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { 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/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java index 3621ab975daf..b0265c07363f 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 class NavigationBarTransitionsTest extends SysuiTestCase { 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/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 3ca4c594cede..0e4d892438ba 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.times; 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.KeyguardUpdateMonitor; 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.PrimaryBouncerCallbackInte 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @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 class StatusBarKeyguardViewManagerTest extends SysuiTestCase { 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void dismissWithAction_AfterKeyguardGoneSetToFalse() { OnDismissAction action = () -> false; Runnable cancelAction = () -> { @@ -265,6 +271,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */); mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */); verify(mPrimaryBouncerInteractor, never()).show(anyBoolean()); + verify(mDeviceEntryInteractor, never()).attemptDeviceEntry(); } @Test @@ -274,9 +281,11 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void onPanelExpansionChanged_showsBouncerWhenSwiping() { mKeyguardStateController.setCanDismissLockScreen(false); mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); @@ -456,6 +466,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void testHiding_cancelsGoneRunnable() { OnDismissAction action = mock(OnDismissAction.class); Runnable cancelAction = mock(Runnable.class); @@ -470,6 +481,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void testHidingBouncer_cancelsGoneRunnable() { OnDismissAction action = mock(OnDismissAction.class); Runnable cancelAction = mock(Runnable.class); @@ -484,6 +496,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void testHiding_doesntCancelWhenShowing() { OnDismissAction action = mock(OnDismissAction.class); Runnable cancelAction = mock(Runnable.class); @@ -539,6 +552,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void testShowAltAuth_unlockingWithBiometricNotAllowed() { // GIVEN cannot use alternate bouncer when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); @@ -553,6 +567,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer + @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void handleDispatchTouchEvent_shouldInterceptTouchAndHandleTouch() { mStatusBarKeyguardViewManager.addCallback(mCallback); @@ -855,6 +874,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer + @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void handleDispatchTouchEvent_shouldInterceptTouchButNotHandleTouch() { mStatusBarKeyguardViewManager.addCallback(mCallback); @@ -881,6 +902,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void shouldInterceptTouch_alternateBouncerNotVisible() { // GIVEN the alternate bouncer is not visible when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); @@ -898,6 +920,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer + @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void alternateBouncerOnTouch_actionDownThenUp_noMinTimeShown_noHideAltBouncer() { reset(mAlternateBouncerInteractor); @@ -955,6 +981,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer + @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void alternateBouncerOnTouch_actionDownThenUp_handlesTouch_hidesAltBouncer() { reset(mAlternateBouncerInteractor); @@ -979,6 +1007,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void alternateBouncerOnTouch_actionUp_doesNotHideAlternateBouncer() { reset(mAlternateBouncerInteractor); @@ -996,6 +1025,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void testShowBouncerOrKeyguard_needsFullScreen() { when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( KeyguardSecurityModel.SecurityMode.SimPin); @@ -1033,6 +1065,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() { when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( KeyguardSecurityModel.SecurityMode.SimPin); @@ -1043,6 +1076,20 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @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/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index a1021f65bd5a..f436a68aa5be 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.broadcast.broadcastDispatcher 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.testDispatcher 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 @@ val Kosmos.shortcutHelperCategoriesRepository by testDispatcher, shortcutHelperSystemShortcutsSource, shortcutHelperMultiTaskingShortcutsSource, + shortcutHelperAppCategoriesShortcutsSource, shortcutHelperInputShortcutsSource, fakeInputManager.inputManager, shortcutHelperStateRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt index 24e47b0af3fa..550ecb3ea7af 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteract import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor @@ -34,5 +35,6 @@ val Kosmos.lockscreenContentViewModel by shadeInteractor = shadeInteractor, applicationScope = applicationCoroutineScope, unfoldTransitionInteractor = unfoldTransitionInteractor, + occlusionInteractor = sceneContainerOcclusionInteractor, ) } diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java index 4860a274cfa8..8abbe5666d58 100644 --- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java +++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java @@ -792,10 +792,11 @@ public class TarBackupReader { } private String getVToUAllowlist(Context context, int userId) { - return Settings.Secure.getStringForUser( + String allowlist = Settings.Secure.getStringForUser( context.getContentResolver(), Settings.Secure.V_TO_U_RESTORE_ALLOWLIST, userId); + return (allowlist == null) ? "" : allowlist; } private static long extractRadix(byte[] data, int offset, int maxChars, int radix) diff --git a/services/core/java/com/android/server/CertBlacklister.java b/services/core/java/com/android/server/CertBlocklister.java index e726c6abfac3..9e23f884f4ba 100644 --- a/services/core/java/com/android/server/CertBlacklister.java +++ b/services/core/java/com/android/server/CertBlocklister.java @@ -16,37 +16,39 @@ package com.android.server; -import android.content.Context; import android.content.ContentResolver; +import android.content.Context; import android.database.ContentObserver; import android.os.Binder; import android.os.FileUtils; import android.provider.Settings; import android.util.Slog; +import libcore.io.IoUtils; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import libcore.io.IoUtils; - /** - * <p>CertBlacklister provides a simple mechanism for updating the platform denylists for SSL + * <p>CertBlocklister provides a simple mechanism for updating the platform denylists for SSL * certificate public keys and serial numbers. */ -public class CertBlacklister extends Binder { +public class CertBlocklister extends Binder { - private static final String TAG = "CertBlacklister"; + private static final String TAG = "CertBlocklister"; private static final String DENYLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/"; + /* For compatibility reasons, the name of these paths cannot be changed */ public static final String PUBKEY_PATH = DENYLIST_ROOT + "pubkey_blacklist.txt"; public static final String SERIAL_PATH = DENYLIST_ROOT + "serial_blacklist.txt"; - public static final String PUBKEY_BLACKLIST_KEY = "pubkey_blacklist"; - public static final String SERIAL_BLACKLIST_KEY = "serial_blacklist"; + /* For compatibility reasons, the name of these keys cannot be changed */ + public static final String PUBKEY_BLOCKLIST_KEY = "pubkey_blacklist"; + public static final String SERIAL_BLOCKLIST_KEY = "serial_blacklist"; - private static class BlacklistObserver extends ContentObserver { + private static class BlocklistObserver extends ContentObserver { private final String mKey; private final String mName; @@ -54,7 +56,7 @@ public class CertBlacklister extends Binder { private final File mTmpDir; private final ContentResolver mContentResolver; - public BlacklistObserver(String key, String name, String path, ContentResolver cr) { + BlocklistObserver(String key, String name, String path, ContentResolver cr) { super(null); mKey = key; mName = name; @@ -66,59 +68,61 @@ public class CertBlacklister extends Binder { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); - writeDenylist(); + new Thread("BlocklistUpdater") { + public void run() { + writeDenylist(); + } + }.start(); } public String getValue() { - return Settings.Secure.getString(mContentResolver, mKey); + return Settings.Secure.getStringForUser( + mContentResolver, mKey, mContentResolver.getUserId()); } private void writeDenylist() { - new Thread("BlacklistUpdater") { - public void run() { - synchronized(mTmpDir) { - String blacklist = getValue(); - if (blacklist != null) { - Slog.i(TAG, "Certificate blacklist changed, updating..."); - FileOutputStream out = null; - try { - // create a temporary file - File tmp = File.createTempFile("journal", "", mTmpDir); - // mark it -rw-r--r-- - tmp.setReadable(true, false); - // write to it - out = new FileOutputStream(tmp); - out.write(blacklist.getBytes()); - // sync to disk - FileUtils.sync(out); - // atomic rename - tmp.renameTo(new File(mPath)); - Slog.i(TAG, "Certificate blacklist updated"); - } catch (IOException e) { - Slog.e(TAG, "Failed to write blacklist", e); - } finally { - IoUtils.closeQuietly(out); - } - } - } + synchronized (mTmpDir) { + String blocklist = getValue(); + if (blocklist == null) { + return; } - }.start(); + if (mPath.equals(SERIAL_PATH)) { + Slog.w(TAG, "The certificate blocklist based on serials is deprecated. " + + "Please use the pubkey blocklist instead."); + } + Slog.i(TAG, "Certificate blocklist changed, updating..."); + FileOutputStream out = null; + try { + // Create a temporary file and rename it atomically. + File tmp = File.createTempFile("journal", "", mTmpDir); + tmp.setReadable(true /* readable */, false /* ownerOnly */); + out = new FileOutputStream(tmp); + out.write(blocklist.getBytes()); + FileUtils.sync(out); + tmp.renameTo(new File(mPath)); + Slog.i(TAG, "Certificate blocklist updated"); + } catch (IOException e) { + Slog.e(TAG, "Failed to write blocklist", e); + } finally { + IoUtils.closeQuietly(out); + } + } } } - public CertBlacklister(Context context) { + public CertBlocklister(Context context) { registerObservers(context.getContentResolver()); } - private BlacklistObserver buildPubkeyObserver(ContentResolver cr) { - return new BlacklistObserver(PUBKEY_BLACKLIST_KEY, + private BlocklistObserver buildPubkeyObserver(ContentResolver cr) { + return new BlocklistObserver(PUBKEY_BLOCKLIST_KEY, "pubkey", PUBKEY_PATH, cr); } - private BlacklistObserver buildSerialObserver(ContentResolver cr) { - return new BlacklistObserver(SERIAL_BLACKLIST_KEY, + private BlocklistObserver buildSerialObserver(ContentResolver cr) { + return new BlocklistObserver(SERIAL_BLOCKLIST_KEY, "serial", SERIAL_PATH, cr); @@ -127,16 +131,16 @@ public class CertBlacklister extends Binder { private void registerObservers(ContentResolver cr) { // set up the public key denylist observer cr.registerContentObserver( - Settings.Secure.getUriFor(PUBKEY_BLACKLIST_KEY), - true, - buildPubkeyObserver(cr) + Settings.Secure.getUriFor(PUBKEY_BLOCKLIST_KEY), + true, + buildPubkeyObserver(cr) ); // set up the serial number denylist observer cr.registerContentObserver( - Settings.Secure.getUriFor(SERIAL_BLACKLIST_KEY), - true, - buildSerialObserver(cr) + Settings.Secure.getUriFor(SERIAL_BLOCKLIST_KEY), + true, + buildSerialObserver(cr) ); } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index bc83a0edd918..bacfd8f9960e 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -921,8 +921,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { //helper function to determine if limit on num listeners applies to callingUid private boolean doesLimitApplyForListeners(int callingUid, int exemptUid) { - return (callingUid != Process.SYSTEM_UID - && callingUid != Process.PHONE_UID + return (!TelephonyPermissions.isSystemOrPhone(callingUid) && callingUid != exemptUid); } diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 9eef6579d2df..e46397bc8ab7 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 @@ class DisplayManagerShellCommand extends ShellCommand { 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 @@ class DisplayManagerShellCommand extends ShellCommand { 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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index b0d734df37b4..0dbaaf398e00 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. hideStatusBarIconLocked(); getUserData(userId).mInFullscreenMode = false; mWindowManagerInternal.setDismissImeOnBackKeyPressed(false); + scheduleResetStylusHandwriting(); } @BinderThread 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 2ec9bdb75349..3aea6d533295 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 @@ abstract class ContextHubServiceTransaction { @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 @@ abstract class ContextHubServiceTransaction { 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 @@ abstract class ContextHubServiceTransaction { 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 @@ abstract class ContextHubServiceTransaction { 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 @@ abstract class ContextHubServiceTransaction { return mTransactionType; } - Integer getMessageSequenceNumber() { + int getMessageSequenceNumber() { return mMessageSequenceNumber; } @@ -204,14 +204,14 @@ abstract class ContextHubServiceTransaction { 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 1a449e024ee1..e6d330f85dfc 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 @@ import java.util.concurrent.atomic.AtomicInteger; 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 @@ import java.util.concurrent.atomic.AtomicInteger; 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 614a0a59c691..5a9cf0326244 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 @@ public final class NotificationAttentionHelper { private final boolean mUseAttentionLight; boolean mHasLight; + private final boolean mEnableNotificationAccessibilityEvents; private final SettingsObserver mSettingsObserver; @@ -190,6 +191,9 @@ public final class NotificationAttentionHelper { 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 @@ public final class NotificationAttentionHelper { } void sendAccessibilityEvent(NotificationRecord record) { - if (!mAccessibilityManager.isEnabled()) { + if (!mAccessibilityManager.isEnabled() || !mEnableNotificationAccessibilityEvents) { return; } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 3a0c1d0e8a36..c09504fa36c8 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -196,9 +196,12 @@ public class PreferencesHelper implements RankingConfig { int USER_LOCKED_BUBBLE = 0x00000002; } + private final Object mLock = new Object(); // pkg|uid => PackagePreferences + @GuardedBy("mLock") private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>(); // pkg|userId => PackagePreferences + @GuardedBy("mLock") private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>(); private final Context mContext; @@ -270,7 +273,7 @@ public class PreferencesHelper implements RankingConfig { Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW); } - synchronized (mPackagePreferences) { + synchronized (mLock) { while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { tag = parser.getName(); if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) { @@ -492,6 +495,7 @@ public class PreferencesHelper implements RankingConfig { DEFAULT_BUBBLE_PREFERENCE, mClock.millis()); } + @GuardedBy("mLock") private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, @UserIdInt int userId, int uid, int importance, int priority, int visibility, boolean showBadge, int bubblePreference, long creationTime) { @@ -661,7 +665,7 @@ public class PreferencesHelper implements RankingConfig { notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId); } - synchronized (mPackagePreferences) { + synchronized (mLock) { final int N = mPackagePreferences.size(); for (int i = 0; i < N; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); @@ -670,11 +674,10 @@ public class PreferencesHelper implements RankingConfig { } writePackageXml(r, out, notifPermissions, forBackup); } - } - if (Flags.persistIncompleteRestoreData() && !forBackup) { - synchronized (mRestoredWithoutUids) { - final int N = mRestoredWithoutUids.size(); - for (int i = 0; i < N; i++) { + + if (Flags.persistIncompleteRestoreData() && !forBackup) { + final int M = mRestoredWithoutUids.size(); + for (int i = 0; i < M; i++) { final PackagePreferences r = mRestoredWithoutUids.valueAt(i); writePackageXml(r, out, notifPermissions, false); } @@ -777,7 +780,7 @@ public class PreferencesHelper implements RankingConfig { */ public void setBubblesAllowed(String pkg, int uid, int bubblePreference) { boolean changed; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid); changed = p.bubblePreference != bubblePreference; p.bubblePreference = bubblePreference; @@ -797,20 +800,20 @@ public class PreferencesHelper implements RankingConfig { */ @Override public int getBubblePreference(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(pkg, uid).bubblePreference; } } public int getAppLockedFields(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(pkg, uid).lockedAppFields; } } @Override public boolean canShowBadge(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(packageName, uid).showBadge; } } @@ -818,7 +821,7 @@ public class PreferencesHelper implements RankingConfig { @Override public void setShowBadge(String packageName, int uid, boolean showBadge) { boolean changed = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid); if (pkgPrefs.showBadge != showBadge) { pkgPrefs.showBadge = showBadge; @@ -831,28 +834,28 @@ public class PreferencesHelper implements RankingConfig { } public boolean isInInvalidMsgState(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.hasSentInvalidMessage && !r.hasSentValidMessage; } } public boolean hasUserDemotedInvalidMsgApp(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return isInInvalidMsgState(packageName, uid) ? r.userDemotedMsgApp : false; } } public void setInvalidMsgAppDemoted(String packageName, int uid, boolean isDemoted) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); r.userDemotedMsgApp = isDemoted; } } public boolean setInvalidMessageSent(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); boolean valueChanged = r.hasSentInvalidMessage == false; r.hasSentInvalidMessage = true; @@ -862,7 +865,7 @@ public class PreferencesHelper implements RankingConfig { } public boolean setValidMessageSent(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); boolean valueChanged = r.hasSentValidMessage == false; r.hasSentValidMessage = true; @@ -873,7 +876,7 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting boolean hasSentInvalidMsg(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.hasSentInvalidMessage; } @@ -881,7 +884,7 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting boolean hasSentValidMsg(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.hasSentValidMessage; } @@ -889,7 +892,7 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting boolean didUserEverDemoteInvalidMsgApp(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.userDemotedMsgApp; } @@ -897,7 +900,7 @@ public class PreferencesHelper implements RankingConfig { /** Sets whether this package has sent a notification with valid bubble metadata. */ public boolean setValidBubbleSent(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); boolean valueChanged = !r.hasSentValidBubble; r.hasSentValidBubble = true; @@ -906,14 +909,14 @@ public class PreferencesHelper implements RankingConfig { } boolean hasSentValidBubble(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.hasSentValidBubble; } } boolean isImportanceLocked(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); return r.fixedImportance || r.defaultAppLockedImportance; } @@ -924,7 +927,7 @@ public class PreferencesHelper implements RankingConfig { if (groupId == null) { return false; } - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); NotificationChannelGroup group = r.groups.get(groupId); if (group == null) { @@ -935,13 +938,13 @@ public class PreferencesHelper implements RankingConfig { } int getPackagePriority(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(pkg, uid).priority; } } int getPackageVisibility(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(pkg, uid).visibility; } } @@ -956,7 +959,7 @@ public class PreferencesHelper implements RankingConfig { throw new IllegalArgumentException("group.getName() can't be empty"); } boolean needsDndChange = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -1010,7 +1013,7 @@ public class PreferencesHelper implements RankingConfig { && channel.getImportance() <= IMPORTANCE_MAX, "Invalid importance level"); boolean needsPolicyFileChange = false, wasUndeleted = false, needsDndChange = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -1154,7 +1157,7 @@ public class PreferencesHelper implements RankingConfig { void unlockNotificationChannelImportance(String pkg, int uid, String updatedChannelId) { Objects.requireNonNull(updatedChannelId); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -1176,7 +1179,7 @@ public class PreferencesHelper implements RankingConfig { Objects.requireNonNull(updatedChannel.getId()); boolean changed = false; boolean needsDndChange = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -1351,7 +1354,7 @@ public class PreferencesHelper implements RankingConfig { String channelId, String conversationId, boolean returnParentIfNoConversationChannel, boolean includeDeleted) { Preconditions.checkNotNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { return null; @@ -1392,7 +1395,7 @@ public class PreferencesHelper implements RankingConfig { Preconditions.checkNotNull(pkg); Preconditions.checkNotNull(conversationId); List<NotificationChannel> channels = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { return channels; @@ -1412,7 +1415,7 @@ public class PreferencesHelper implements RankingConfig { int callingUid, boolean fromSystemOrSystemUi) { boolean deletedChannel = false; boolean channelBypassedDnd = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return false; @@ -1448,7 +1451,7 @@ public class PreferencesHelper implements RankingConfig { public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) { Objects.requireNonNull(pkg); Objects.requireNonNull(channelId); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return; @@ -1460,7 +1463,7 @@ public class PreferencesHelper implements RankingConfig { @Override public void permanentlyDeleteNotificationChannels(String pkg, int uid) { Objects.requireNonNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return; @@ -1491,7 +1494,7 @@ public class PreferencesHelper implements RankingConfig { boolean fixed = mPermissionHelper.isPermissionFixed( pi.packageName, user.getUserHandle().getIdentifier()); if (fixed) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getOrCreatePackagePreferencesLocked( pi.packageName, pi.applicationInfo.uid); p.fixedImportance = true; @@ -1506,7 +1509,7 @@ public class PreferencesHelper implements RankingConfig { public void updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<Pair<String, Integer>> toAdd) { - synchronized (mPackagePreferences) { + synchronized (mLock) { for (PackagePreferences p : mPackagePreferences.values()) { if (userId == UserHandle.getUserId(p.uid)) { if (toRemove != null && toRemove.contains(p.pkg)) { @@ -1536,7 +1539,7 @@ public class PreferencesHelper implements RankingConfig { public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg, int uid, String groupId, boolean includeDeleted) { Objects.requireNonNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null || groupId == null || !r.groups.containsKey(groupId)) { return null; @@ -1559,7 +1562,7 @@ public class PreferencesHelper implements RankingConfig { public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg, int uid) { Objects.requireNonNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return null; @@ -1573,7 +1576,7 @@ public class PreferencesHelper implements RankingConfig { boolean includeBlocked, Set<String> activeChannelFilter) { Objects.requireNonNull(pkg); Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return ParceledListSlice.emptyList(); @@ -1624,7 +1627,7 @@ public class PreferencesHelper implements RankingConfig { String groupId, int callingUid, boolean fromSystemOrSystemUi) { List<NotificationChannel> deletedChannels = new ArrayList<>(); boolean groupBypassedDnd = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null || TextUtils.isEmpty(groupId)) { return deletedChannels; @@ -1656,7 +1659,7 @@ public class PreferencesHelper implements RankingConfig { public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid) { List<NotificationChannelGroup> groups = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return groups; @@ -1667,7 +1670,7 @@ public class PreferencesHelper implements RankingConfig { } public NotificationChannelGroup getGroupForChannel(String pkg, int uid, String channelId) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getPackagePreferencesLocked(pkg, uid); if (p != null) { NotificationChannel nc = p.channels.get(channelId); @@ -1686,7 +1689,7 @@ public class PreferencesHelper implements RankingConfig { public ArrayList<ConversationChannelWrapper> getConversations(IntArray userIds, boolean onlyImportant) { - synchronized (mPackagePreferences) { + synchronized (mLock) { ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>(); for (PackagePreferences p : mPackagePreferences.values()) { if (userIds.binarySearch(UserHandle.getUserId(p.uid)) >= 0) { @@ -1730,7 +1733,7 @@ public class PreferencesHelper implements RankingConfig { public ArrayList<ConversationChannelWrapper> getConversations(String pkg, int uid) { Objects.requireNonNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return new ArrayList<>(); @@ -1772,7 +1775,7 @@ public class PreferencesHelper implements RankingConfig { public @NonNull List<String> deleteConversations(String pkg, int uid, Set<String> conversationIds, int callingUid, boolean fromSystemOrSystemUi) { List<String> deletedChannelIds = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return deletedChannelIds; @@ -1805,7 +1808,7 @@ public class PreferencesHelper implements RankingConfig { boolean includeDeleted) { Objects.requireNonNull(pkg); List<NotificationChannel> channels = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return ParceledListSlice.emptyList(); @@ -1827,7 +1830,7 @@ public class PreferencesHelper implements RankingConfig { public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg, int uid) { List<NotificationChannel> channels = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { final PackagePreferences r = mPackagePreferences.get( packagePreferencesKey(pkg, uid)); if (r != null) { @@ -1848,7 +1851,7 @@ public class PreferencesHelper implements RankingConfig { * upgrades. */ public boolean onlyHasDefaultChannel(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r.channels.size() == (notificationClassification() ? 5 : 1) && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { @@ -1861,7 +1864,7 @@ public class PreferencesHelper implements RankingConfig { public int getDeletedChannelCount(String pkg, int uid) { Objects.requireNonNull(pkg); int deletedCount = 0; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return deletedCount; @@ -1880,7 +1883,7 @@ public class PreferencesHelper implements RankingConfig { public int getBlockedChannelCount(String pkg, int uid) { Objects.requireNonNull(pkg); int blockedCount = 0; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return blockedCount; @@ -1923,7 +1926,7 @@ public class PreferencesHelper implements RankingConfig { ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>(); final IntArray currentUserIds = mUserProfiles.getCurrentProfileIds(); - synchronized (mPackagePreferences) { + synchronized (mLock) { final int numPackagePreferences = mPackagePreferences.size(); for (int i = 0; i < numPackagePreferences; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); @@ -1992,7 +1995,7 @@ public class PreferencesHelper implements RankingConfig { * considered for sentiment adjustments (and thus never show a blocking helper). */ public void setAppImportanceLocked(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getOrCreatePackagePreferencesLocked(packageName, uid); if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) { return; @@ -2008,7 +2011,7 @@ public class PreferencesHelper implements RankingConfig { * Returns the delegate for a given package, if it's allowed by the package and the user. */ public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); if (prefs == null || prefs.delegate == null) { @@ -2026,7 +2029,7 @@ public class PreferencesHelper implements RankingConfig { */ public void setNotificationDelegate(String sourcePkg, int sourceUid, String delegatePkg, int delegateUid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid); prefs.delegate = new Delegate(delegatePkg, delegateUid, true); } @@ -2036,7 +2039,7 @@ public class PreferencesHelper implements RankingConfig { * Used by an app to turn off its notification delegate. */ public void revokeNotificationDelegate(String sourcePkg, int sourceUid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); if (prefs != null && prefs.delegate != null) { prefs.delegate.mEnabled = false; @@ -2050,7 +2053,7 @@ public class PreferencesHelper implements RankingConfig { */ public boolean isDelegateAllowed(String sourcePkg, int sourceUid, String potentialDelegatePkg, int potentialDelegateUid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, @@ -2096,24 +2099,25 @@ public class PreferencesHelper implements RankingConfig { pw.println("per-package config version: " + XML_VERSION); pw.println("PackagePreferences:"); - synchronized (mPackagePreferences) { + synchronized (mLock) { dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences, pkgPermissions); + pw.println("Restored without uid:"); + dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids, null); } - pw.println("Restored without uid:"); - dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids, null); } public void dump(ProtoOutputStream proto, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) { - synchronized (mPackagePreferences) { + synchronized (mLock) { dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter, mPackagePreferences, pkgPermissions); + dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, + filter, mRestoredWithoutUids, null); } - dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter, - mRestoredWithoutUids, null); } + @GuardedBy("mLock") private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences, @@ -2298,7 +2302,7 @@ public class PreferencesHelper implements RankingConfig { pkgsWithPermissionsToHandle = pkgPermissions.keySet(); } int pulledEvents = 0; - synchronized (mPackagePreferences) { + synchronized (mLock) { for (int i = 0; i < mPackagePreferences.size(); i++) { if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) { break; @@ -2378,7 +2382,7 @@ public class PreferencesHelper implements RankingConfig { * {@link StatsEvent}. */ public void pullPackageChannelPreferencesStats(List<StatsEvent> events) { - synchronized (mPackagePreferences) { + synchronized (mLock) { int totalChannelsPulled = 0; for (int i = 0; i < mPackagePreferences.size(); i++) { if (totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) { @@ -2414,7 +2418,7 @@ public class PreferencesHelper implements RankingConfig { * {@link StatsEvent}. */ public void pullPackageChannelGroupPreferencesStats(List<StatsEvent> events) { - synchronized (mPackagePreferences) { + synchronized (mLock) { int totalGroupsPulled = 0; for (int i = 0; i < mPackagePreferences.size(); i++) { if (totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) { @@ -2443,10 +2447,12 @@ public class PreferencesHelper implements RankingConfig { ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) { JSONObject ranking = new JSONObject(); JSONArray PackagePreferencess = new JSONArray(); - try { - ranking.put("noUid", mRestoredWithoutUids.size()); - } catch (JSONException e) { - // pass + synchronized (mLock) { + try { + ranking.put("noUid", mRestoredWithoutUids.size()); + } catch (JSONException e) { + // pass + } } // Track data that we've handled from the permissions-based list @@ -2455,7 +2461,7 @@ public class PreferencesHelper implements RankingConfig { pkgsWithPermissionsToHandle = pkgPermissions.keySet(); } - synchronized (mPackagePreferences) { + synchronized (mLock) { final int N = mPackagePreferences.size(); for (int i = 0; i < N; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); @@ -2561,7 +2567,7 @@ public class PreferencesHelper implements RankingConfig { } public Map<Integer, String> getPackageBans() { - synchronized (mPackagePreferences) { + synchronized (mLock) { final int N = mPackagePreferences.size(); ArrayMap<Integer, String> packageBans = new ArrayMap<>(N); for (int i = 0; i < N; i++) { @@ -2620,7 +2626,7 @@ public class PreferencesHelper implements RankingConfig { private Map<String, Integer> getPackageChannels() { ArrayMap<String, Integer> packageChannels = new ArrayMap<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { for (int i = 0; i < mPackagePreferences.size(); i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); int channelCount = 0; @@ -2636,7 +2642,7 @@ public class PreferencesHelper implements RankingConfig { } public void onUserRemoved(int userId) { - synchronized (mPackagePreferences) { + synchronized (mLock) { int N = mPackagePreferences.size(); for (int i = N - 1; i >= 0; i--) { PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i); @@ -2648,7 +2654,7 @@ public class PreferencesHelper implements RankingConfig { } protected void onLocaleChanged(Context context, int userId) { - synchronized (mPackagePreferences) { + synchronized (mLock) { int N = mPackagePreferences.size(); for (int i = 0; i < N; i++) { PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i); @@ -2678,22 +2684,24 @@ public class PreferencesHelper implements RankingConfig { for (int i = 0; i < size; i++) { final String pkg = pkgList[i]; final int uid = uidList[i]; - synchronized (mPackagePreferences) { + synchronized (mLock) { mPackagePreferences.remove(packagePreferencesKey(pkg, uid)); + mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId)); } - mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId)); updated = true; } } else { for (String pkg : pkgList) { - // Package install - final PackagePreferences r = - mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId)); - if (r != null) { - try { - r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId); - mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId)); - synchronized (mPackagePreferences) { + try { + // Package install + int uid = mPm.getPackageUidAsUser(pkg, changeUserId); + PackagePermission p = null; + synchronized (mLock) { + final PackagePreferences r = + mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId)); + if (r != null) { + r.uid = uid; + mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId)); mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r); // Try to restore any unrestored sound resources @@ -2715,32 +2723,29 @@ public class PreferencesHelper implements RankingConfig { channel.setSound(restoredUri, channel.getAudioAttributes()); } } - } - if (r.migrateToPm) { - try { - PackagePermission p = new PackagePermission( + + if (r.migrateToPm) { + p = new PackagePermission( r.pkg, UserHandle.getUserId(r.uid), r.importance != IMPORTANCE_NONE, hasUserConfiguredSettings(r)); - mPermissionHelper.setNotificationPermission(p); - } catch (Exception e) { - Slog.e(TAG, "could not migrate setting for " + r.pkg, e); } + updated = true; } - updated = true; - } catch (Exception e) { - Slog.e(TAG, "could not restore " + r.pkg, e); } + if (p != null) { + mPermissionHelper.setNotificationPermission(p); + } + } catch (Exception e) { + Slog.e(TAG, "could not restore " + pkg, e); } // Package upgrade try { - synchronized (mPackagePreferences) { - PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg, - mPm.getPackageUidAsUser(pkg, changeUserId)); - if (fullPackagePreferences != null) { - updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences); - updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences); - } + PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg, + mPm.getPackageUidAsUser(pkg, changeUserId)); + if (fullPackagePreferences != null) { + updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences); + updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences); } } catch (PackageManager.NameNotFoundException e) { } @@ -2754,7 +2759,7 @@ public class PreferencesHelper implements RankingConfig { } public void clearData(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getPackagePreferencesLocked(pkg, uid); if (p != null) { p.channels = new ArrayMap<>(); @@ -2941,7 +2946,7 @@ public class PreferencesHelper implements RankingConfig { } public void unlockAllNotificationChannels() { - synchronized (mPackagePreferences) { + synchronized (mLock) { final int numPackagePreferences = mPackagePreferences.size(); for (int i = 0; i < numPackagePreferences; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); @@ -2958,7 +2963,7 @@ public class PreferencesHelper implements RankingConfig { PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL), user.getUserHandle().getIdentifier()); for (PackageInfo pi : packages) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getOrCreatePackagePreferencesLocked( pi.packageName, pi.applicationInfo.uid); if (p.migrateToPm && p.uid != UNKNOWN_UID) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c0b8034b9a56..2e63cdbf1823 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -186,6 +186,7 @@ import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.telephony.CarrierAppUtils; +import com.android.internal.telephony.TelephonyPermissions; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.ConcurrentUtils; @@ -4492,8 +4493,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService void setSystemAppHiddenUntilInstalled(@NonNull Computer snapshot, String packageName, boolean hidden) { final int callingUid = Binder.getCallingUid(); - final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID - || callingUid == Process.SYSTEM_UID; + final boolean calledFromSystemOrPhone = TelephonyPermissions.isSystemOrPhone(callingUid); if (!calledFromSystemOrPhone) { mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, "setSystemAppHiddenUntilInstalled"); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index ff8abf879487..924b36cef79a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -92,6 +92,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.telephony.TelephonyPermissions; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.HexDump; @@ -356,7 +357,7 @@ public class PackageManagerServiceUtils { * If not, throws a {@link SecurityException}. */ public static void enforceSystemOrPhoneCaller(String methodName, int callingUid) { - if (callingUid != Process.PHONE_UID && callingUid != Process.SYSTEM_UID) { + if (!TelephonyPermissions.isSystemOrPhone(callingUid)) { throw new SecurityException( "Cannot call " + methodName + " from UID " + callingUid); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 0cda30f3affc..c9ba683a698a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5496,9 +5496,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { // 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 +5660,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 +5720,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 +6226,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // 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 b0f92e8f1c2b..129cee77f971 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 c0881180af94..3b0b727597a5 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 @@ public abstract class ActivityTaskManagerInternal { 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 ded205e5eafd..5b178750bf55 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @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 00b6453b2484..f5757dc7a4de 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -189,9 +189,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // 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 +2090,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { 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 +2097,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // 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 +2120,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } - // 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); diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index 30f2d0d64d13..6abef8b9a048 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 @@ class DragDropController { 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 @@ class DragDropController { } /** + * 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 4be5bad644f7..ba74f5076bc0 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.graphics.Rect; 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 @@ class DragState { 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 @@ class DragState { } } 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 @@ class DragState { 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 @@ class DragState { 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 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 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); + 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 @@ class DragState { } 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 6f2528085d74..7aede8b4a068 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.ParceledListSlice; 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.SparseArray; 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 @@ class RecentTasks { 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 d3fc7f36eac8..99697de6e2fa 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> 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 b452131188c6..8c7b6375a72f 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 @@ public class SafeActivityOptions { 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 310516b09530..75e3e6547aa8 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { * 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 33a649be43d0..79fec231d421 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 @@ class Task extends TaskFragment { */ 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 @@ class Task extends TaskFragment { mLaunchCookie = _launchCookie; mDeferTaskAppear = _deferTaskAppear; mRemoveWithTaskOrganizer = _removeWithTaskOrganizer; + mIsTrimmableFromRecents = true; EventLogTags.writeWmTaskCreated(mTaskId); } @@ -3856,6 +3863,7 @@ class Task extends TaskFragment { 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 @@ class Task extends TaskFragment { } } + 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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index e9004880f506..bc32b91f2c57 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 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.WindowContainerToken; 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub 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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 791d030a6b63..e5a1ebfdc404 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -2445,11 +2445,11 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } - t.traceBegin("CertBlacklister"); + t.traceBegin("CertBlocklister"); try { - CertBlacklister blacklister = new CertBlacklister(context); + CertBlocklister blocklister = new CertBlocklister(context); } catch (Throwable e) { - reportWtf("starting CertBlacklister", e); + reportWtf("starting CertBlocklister", e); } t.traceEnd(); diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index c76bcf804ee4..3ed6ad78343b 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 @@ public final class ProfcollectForwardingService extends SystemService { 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 17d9ef9fad34..f83144f176d3 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.verify; 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 @@ public class InputMethodManagerServiceTestBase { 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/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java index 7aec42b7eceb..00daf415375e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java @@ -43,6 +43,7 @@ import static java.util.Collections.singleton; import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; @@ -142,6 +143,7 @@ public class TrustManagerServiceTest { private final Map<ComponentName, ITrustAgentService.Stub> mMockTrustAgents = new HashMap<>(); private @Mock ActivityManager mActivityManager; + private @Mock ActivityManagerInternal mActivityManagerInternal; private @Mock AlarmManager mAlarmManager; private @Mock BiometricManager mBiometricManager; private @Mock DevicePolicyManager mDevicePolicyManager; @@ -158,6 +160,7 @@ public class TrustManagerServiceTest { private HandlerThread mHandlerThread; private TrustManagerService mService; private ITrustManager mTrustManager; + private ActivityManagerInternal mPreviousActivityManagerInternal; @Before public void setUp() throws Exception { @@ -210,6 +213,11 @@ public class TrustManagerServiceTest { mMockContext.setMockPackageManager(mPackageManager); mMockContext.addMockSystemService(UserManager.class, mUserManager); doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService()); + mPreviousActivityManagerInternal = LocalServices.getService( + ActivityManagerInternal.class); + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.addService(ActivityManagerInternal.class, + mActivityManagerInternal); LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class)); grantPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE); @@ -257,7 +265,14 @@ public class TrustManagerServiceTest { @After public void tearDown() { LocalServices.removeServiceForTest(SystemServiceManager.class); - mHandlerThread.quit(); + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + if (mPreviousActivityManagerInternal != null) { + LocalServices.addService(ActivityManagerInternal.class, + mPreviousActivityManagerInternal); + } + if (mHandlerThread != null) { + mHandlerThread.quit(); + } } @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 2233aa2eed0f..28a5db904bd9 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 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { 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 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { 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/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index d151345c287e..559c32413d70 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -188,6 +188,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; @SmallTest @@ -489,6 +490,34 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mPm.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); } + private static void testThreadSafety(Runnable operationToTest, int nThreads, + int nRunsPerThread) throws Exception { + final CountDownLatch startLatch = new CountDownLatch(1); + final CountDownLatch doneLatch = new CountDownLatch(nThreads); + + for (int i = 0; i < nThreads; i++) { + Runnable threadRunnable = () -> { + try { + startLatch.await(); + for (int j = 0; j < nRunsPerThread; j++) { + operationToTest.run(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + doneLatch.countDown(); + } + }; + new Thread(threadRunnable, "Test Thread #" + i).start(); + } + + // Ready set go + startLatch.countDown(); + + // Wait for all test threads to be done. + doneLatch.await(); + } + @Test public void testWriteXml_onlyBackupsTargetUser() throws Exception { // Setup package notifications. @@ -6193,6 +6222,36 @@ public class PreferencesHelperTest extends UiServiceTestCase { .isEqualTo(IMPORTANCE_LOW); } + + @Test + public void testRestoredWithoutUid_threadSafety() throws Exception { + when(mPm.getPackageUidAsUser(anyString(), anyInt())).thenReturn(UNKNOWN_UID); + when(mPm.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException()); + when(mClock.millis()).thenReturn(System.currentTimeMillis()); + testThreadSafety(() -> { + String id = "id"; + String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + Thread.currentThread()+ "\" show_badge=\"true\">\n" + + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" " + + "show_badge=\"true\" />\n" + + "</package>\n" + + "<package name=\"" + PKG_P + "\" show_badge=\"true\">\n" + + "</package>\n" + + "</ranking>\n"; + + try { + loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // trigger a removal from the list + mXmlHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_P}, + new int[]{UNKNOWN_UID}); + }, 20, 50); + } + private static NotificationChannel cloneChannel(NotificationChannel original) { Parcel parcel = Parcel.obtain(); try { 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 248091371ff1..07934ea90b7e 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 @@ public class PhoneWindowManagerTests { @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 @@ public class PhoneWindowManagerTests { // 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 @@ public class PhoneWindowManagerTests { 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 7faf2aacc0bc..8cdb574a967b 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 class DragDropControllerTests extends WindowTestsBase { 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 @@ public class DragDropControllerTests extends WindowTestsBase { .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 7efbc88833cf..35cff7a7a324 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 android.platform.test.annotations.Presubmit; 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 @@ public class ProtoLogIntegrationTest { 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 6ec1429200e6..33f7035dbf18 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 @@ public class RecentTasksTest extends WindowTestsBase { } @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/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index d4b6c91eb7a0..feea55bff125 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -32,6 +32,8 @@ import android.os.UserHandle; import android.util.Log; import android.widget.Toast; +import com.android.internal.telephony.TelephonyPermissions; +import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.TelephonyUtils; /** @@ -310,10 +312,18 @@ public final class LocationAccessPolicy { // This avoid breaking legacy code that rely on public-facing APIs to access cell location, // and it doesn't create an info leak risk because the cell location is stored in the phone // process anyway, and the system server already has location access. - if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID - || query.callingUid == Process.NETWORK_STACK_UID - || query.callingUid == Process.ROOT_UID) { - return LocationPermissionResult.ALLOWED; + if (Flags.supportPhoneUidCheckForMultiuser()) { + if (TelephonyPermissions.isSystemOrPhone(query.callingUid) + || UserHandle.isSameApp(query.callingUid, Process.NETWORK_STACK_UID) + || UserHandle.isSameApp(query.callingUid, Process.ROOT_UID)) { + return LocationPermissionResult.ALLOWED; + } + } else { + if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID + || query.callingUid == Process.NETWORK_STACK_UID + || query.callingUid == Process.ROOT_UID) { + return LocationPermissionResult.ALLOWED; + } } // Check the system-wide requirements. If the location main switch is off and the caller is diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java index 796c82dd4af7..3e6f743e8a93 100644 --- a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java +++ b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java @@ -43,12 +43,17 @@ public final class ProvisionSubscriberId implements Parcelable { /** 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 final class ProvisionSubscriberId implements Parcelable { 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 @@ public final class ProvisionSubscriberId implements Parcelable { } /** - * @return token. + * @return provision subscriberId. * @hide */ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) @@ -106,6 +112,15 @@ public final class ProvisionSubscriberId implements Parcelable { 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 @@ public final class ProvisionSubscriberId implements Parcelable { 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 @@ public final class ProvisionSubscriberId implements Parcelable { 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 5a27593c7a36..5a48327e7576 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.IOException; 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 @@ public class LegacyProtoLogImplTest { //noinspection ResultOfMethodCallIgnored mFile.delete(); mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename, - 1024 * 1024, mReader, 1024, new TreeMap<>(), () -> {}); + 1024 * 1024, mReader, 1024, () -> {}); } @After @@ -142,7 +141,7 @@ public class LegacyProtoLogImplTest { 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 @@ public class LegacyProtoLogImplTest { 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 @@ public class LegacyProtoLogImplTest { 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 @@ public class LegacyProtoLogImplTest { 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 @@ public class LegacyProtoLogImplTest { 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 @@ public class LegacyProtoLogImplTest { 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 @@ public class LegacyProtoLogImplTest { 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 @@ public class LegacyProtoLogImplTest { 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 1d7b6b348e10..b6672a0e2f4b 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.junit.runners.JUnit4; 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.Random; 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 @@ public class PerfettoProtoLogImplTest { //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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { // 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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { } @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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { .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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { 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 @@ public class PerfettoProtoLogImplTest { ProtoLogImpl.setSingleInstance(mProtoLog); ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1, - 0b11, null, true); + 0b11, true); } finally { traceMonitor.stop(mWriter); } @@ -586,7 +607,7 @@ public class PerfettoProtoLogImplTest { .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 @@ public class PerfettoProtoLogImplTest { 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 60456f9ea10f..0496240f01e4 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 class ProtoLogImplTest { 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 @@ public class ProtoLogImplTest { 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 1087ae6ee41d..3c99e68cd6a0 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 @@ class ProtoLogCallProcessorImpl( 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 2d5b50b528a3..aa530050741b 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 @@ object ProtoLogTool { 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 6a8a0717b2f1..c478f5844de6 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 @@ class SourceTransformer( 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 @@ class SourceTransformer( 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 2a8367787ba1..0cbbd483fe59 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 @@ class EndToEndTest { .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 82aa93da613b..6cde7a72db53 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 SourceTransformerTest { 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 SourceTransformerTest { 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 SourceTransformerTest { 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 SourceTransformerTest { 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 SourceTransformerTest { 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 SourceTransformerTest { 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 @@ class SourceTransformerTest { 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 @@ class SourceTransformerTest { 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 @@ class SourceTransformerTest { 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 @@ class SourceTransformerTest { 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 @@ class SourceTransformerTest { 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 @@ class SourceTransformerTest { 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) } } |