diff options
85 files changed, 1602 insertions, 450 deletions
diff --git a/Android.bp b/Android.bp index 258440f24084..5b9f2cbf2d0d 100644 --- a/Android.bp +++ b/Android.bp @@ -427,6 +427,7 @@ java_defaults { "modules-utils-expresslog", "perfetto_trace_javastream_protos_jarjar", "libaconfig_java_proto_nano", + "aconfig_device_paths_java", ], } diff --git a/core/api/current.txt b/core/api/current.txt index dada20eb14dc..0972f4110d0b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6396,6 +6396,7 @@ package android.app { method public android.graphics.drawable.Icon getLargeIcon(); method @Nullable public android.content.LocusId getLocusId(); method public CharSequence getSettingsText(); + method @FlaggedApi("android.app.api_rich_ongoing") @Nullable public String getShortCriticalText(); method public String getShortcutId(); method public android.graphics.drawable.Icon getSmallIcon(); method public String getSortKey(); @@ -6719,6 +6720,7 @@ package android.app { method @NonNull public android.app.Notification.Builder setPublicVersion(android.app.Notification); method @NonNull public android.app.Notification.Builder setRemoteInputHistory(CharSequence[]); method @NonNull public android.app.Notification.Builder setSettingsText(CharSequence); + method @FlaggedApi("android.app.api_rich_ongoing") @NonNull public android.app.Notification.Builder setShortCriticalText(@Nullable String); method @NonNull public android.app.Notification.Builder setShortcutId(String); method @NonNull public android.app.Notification.Builder setShowWhen(boolean); method @NonNull public android.app.Notification.Builder setSmallIcon(@DrawableRes int); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 7a36fbb55dc4..81d2c890ee31 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1281,6 +1281,15 @@ public class Notification implements Parcelable public static final String EXTRA_BIG_TEXT = "android.bigText"; /** + * {@link #extras} key: very short text summarizing the most critical information contained in + * the notification. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final String EXTRA_SHORT_CRITICAL_TEXT = "android.shortCriticalText"; + + /** * {@link #extras} key: this is the resource ID of the notification's main small icon, as * supplied to {@link Builder#setSmallIcon(int)}. * @@ -4050,6 +4059,17 @@ public class Notification implements Parcelable return String.join("|", defaultStrings); } + + /** + * Returns the very short text summarizing the most critical information contained in the + * notification, or null if this field was not set. + */ + @Nullable + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public String getShortCriticalText() { + return extras.getString(EXTRA_SHORT_CRITICAL_TEXT); + } + /** * @hide */ @@ -4991,6 +5011,18 @@ public class Notification implements Parcelable } /** + * Sets a very short string summarizing the most critical information contained in the + * notification. Suggested max length is 5 characters, and there is no guarantee how much or + * how little of this text will be shown. + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + @NonNull + public Builder setShortCriticalText(@Nullable String shortCriticalText) { + mN.extras.putString(EXTRA_SHORT_CRITICAL_TEXT, shortCriticalText); + return this; + } + + /** * Set the progress this notification represents. * * The platform template will represent this using a {@link ProgressBar}. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 26bb6e4df325..fb2655c771c4 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -8842,6 +8842,7 @@ public abstract class PackageManager { try { ParsedPackage pp = parser2.parsePackage(apkFile, parserFlags, false); + pp.hideAsFinal(); return PackageInfoCommonUtils.generate(pp, flagsBits, UserHandle.myUserId()); } catch (PackageParserException e) { diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index c4d12d4336c6..996a288ef59d 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2066,9 +2066,11 @@ public abstract class BatteryStats { public static final int EVENT_LONG_WAKE_LOCK = 0x0014; // Event for reporting change of some device states, triggered by a specific UID public static final int EVENT_STATE_CHANGE = 0x0015; + // Event for reporting change of screen states. + public static final int EVENT_DISPLAY_STATE_CHANGED = 0x0016; // Number of event types. - public static final int EVENT_COUNT = 0x0016; + public static final int EVENT_COUNT = 0x0017; // Mask to extract out only the type part of the event. public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH); @@ -3079,13 +3081,14 @@ public abstract class BatteryStats { public static final String[] HISTORY_EVENT_NAMES = new String[] { "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn", "active", "pkginst", "pkgunin", "alarm", "stats", "pkginactive", "pkgactive", - "tmpwhitelist", "screenwake", "wakeupap", "longwake", "state" + "tmpwhitelist", "screenwake", "wakeupap", "longwake", "state", + "display_state_changed" }; public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] { "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn", "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa", "Etw", - "Esw", "Ewa", "Elw", "Eec", "Esc" + "Esw", "Ewa", "Elw", "Eec", "Esc", "Eds" }; @FunctionalInterface diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 3d8d933cbbcc..752f174504f2 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -2553,7 +2553,7 @@ public class ZenModeConfig implements Parcelable { if (!Flags.modesUi()) { return manualRule != null; } - return manualRule != null && manualRule.isAutomaticActive(); + return manualRule != null && manualRule.isActive(); } public static class ZenRule implements Parcelable { @@ -2932,8 +2932,7 @@ public class ZenModeConfig implements Parcelable { } } - // TODO: b/363193376 - Rename to isActive() - public boolean isAutomaticActive() { + public boolean isActive() { if (Flags.modesApi() && Flags.modesUi()) { if (!enabled || getPkg() == null) { return false; @@ -3173,7 +3172,7 @@ public class ZenModeConfig implements Parcelable { // DND turned on by an automatic rule for (ZenRule automaticRule : config.automaticRules.values()) { - if (automaticRule.isAutomaticActive()) { + if (automaticRule.isActive()) { if (isValidEventConditionId(automaticRule.conditionId) || isValidScheduleConditionId(automaticRule.conditionId)) { // set text if automatic rule end time is the latest active rule end time diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java index 05c2a9c26709..60a7d6b5a3be 100644 --- a/core/java/android/service/notification/ZenModeDiff.java +++ b/core/java/android/service/notification/ZenModeDiff.java @@ -495,8 +495,8 @@ public class ZenModeDiff { // Even if added or removed, there may be a change in whether or not it was active. // This only applies to automatic rules. - boolean fromActive = from != null ? from.isAutomaticActive() : false; - boolean toActive = to != null ? to.isAutomaticActive() : false; + boolean fromActive = from != null ? from.isActive() : false; + boolean toActive = to != null ? to.isActive() : false; if (fromActive != toActive) { mActiveDiff = new FieldDiff<>(fromActive, toActive); } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index ebcae277c62b..a5e166b95177 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -144,9 +144,9 @@ interface IBatteryStats { @EnforcePermission("UPDATE_DEVICE_STATS") void noteGpsSignalQuality(int signalLevel); @EnforcePermission("UPDATE_DEVICE_STATS") - void noteScreenState(int state); + void noteScreenState(int displayId, int state, int reason); @EnforcePermission("UPDATE_DEVICE_STATS") - void noteScreenBrightness(int brightness); + void noteScreenBrightness(int displayId, int brightness); @EnforcePermission("UPDATE_DEVICE_STATS") void noteUserActivity(int uid, int event); @EnforcePermission("UPDATE_DEVICE_STATS") diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java index b873175451e1..39aadfb24b0c 100644 --- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java +++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java @@ -18,6 +18,7 @@ package com.android.internal.pm.pkg.component; import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; +import android.aconfig.DeviceProtos; import android.aconfig.nano.Aconfig; import android.aconfig.nano.Aconfig.parsed_flag; import android.aconfig.nano.Aconfig.parsed_flags; @@ -40,7 +41,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.util.List; +import java.util.Arrays; import java.util.Map; /** @@ -54,12 +55,6 @@ import java.util.Map; public class AconfigFlags { private static final String LOG_TAG = "AconfigFlags"; - private static final List<String> sTextProtoFilesOnDevice = List.of( - "/system/etc/aconfig_flags.pb", - "/system_ext/etc/aconfig_flags.pb", - "/product/etc/aconfig_flags.pb", - "/vendor/etc/aconfig_flags.pb"); - public enum Permission { READ_WRITE, READ_ONLY @@ -73,7 +68,10 @@ public class AconfigFlags { Slog.v(LOG_TAG, "Feature disabled, skipped all loading"); return; } - for (String fileName : sTextProtoFilesOnDevice) { + final var defaultFlagProtoFiles = + (Process.myUid() == Process.SYSTEM_UID) ? DeviceProtos.parsedFlagsProtoPaths() + : Arrays.asList(DeviceProtos.PATHS); + for (String fileName : defaultFlagProtoFiles) { try (var inputStream = new FileInputStream(fileName)) { loadAconfigDefaultValues(inputStream.readAllBytes()); } catch (IOException e) { diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java index 067e5e8813a7..b23515aa51f3 100644 --- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -37,6 +37,8 @@ public class ScreenDecorationsUtils { * * Note that if the context is not an UI context(not associated with Display), it will use * default display. + * + * If the associated display is not internal, will return 0. */ public static float getWindowCornerRadius(Context context) { final Resources resources = context.getResources(); @@ -44,7 +46,13 @@ public class ScreenDecorationsUtils { return 0f; } // Use Context#getDisplayNoVerify() in case the context is not an UI context. - final String displayUniqueId = context.getDisplayNoVerify().getUniqueId(); + final Display display = context.getDisplayNoVerify(); + // The radius is only valid for internal displays, since the corner radius of external or + // virtual displays is not known when window corners are configured or are not supported. + if (display.getType() != Display.TYPE_INTERNAL) { + return 0f; + } + final String displayUniqueId = display.getUniqueId(); // Radius that should be used in case top or bottom aren't defined. float defaultRadius = RoundedCorners.getRoundedCornerRadius(resources, displayUniqueId) - RoundedCorners.getRoundedCornerRadiusAdjustment(resources, displayUniqueId); diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index ef6ff0518dac..0837b458c3ba 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -215,6 +215,25 @@ public class NotificationTest { } @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void testGetShortCriticalText_noneSet() { + Notification n = new Notification.Builder(mContext, "test") + .build(); + + assertSame(n.getShortCriticalText(), null); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void testGetShortCriticalText_isSet() { + Notification n = new Notification.Builder(mContext, "test") + .setShortCriticalText("short critical text here") + .build(); + + assertSame(n.getShortCriticalText(), "short critical text here"); + } + + @Test public void largeIconMultipleReferences_keptAfterParcelling() { Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource( mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96)); diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp index e0f101229080..74c7b4ca4206 100644 --- a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp +++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp @@ -32,8 +32,8 @@ android_test_helper_app { "truth", ], libs: [ - "android.test.runner", - "android.test.base", + "android.test.runner.stubs.system", + "android.test.base.stubs.system", ], test_suites: [ "device-tests", diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp index 1871203c7600..b6db6d93499d 100644 --- a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp @@ -72,8 +72,8 @@ android_test { "platform-screenshot-diff-core", ], libs: [ - "android.test.base", - "android.test.runner", + "android.test.base.stubs.system", + "android.test.runner.stubs.system", ], jni_libs: [ "libdexmakerjvmtiagent", diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_manage_windows.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_manage_windows.xml new file mode 100644 index 000000000000..7d912a24c443 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_manage_windows.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal"> + <path android:fillColor="@android:color/black" android:pathData="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,440Q80,407 103.5,383.5Q127,360 160,360L240,360L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,520Q880,553 856.5,576.5Q833,600 800,600L720,600L720,800Q720,833 696.5,856.5Q673,880 640,880L160,880ZM160,800L640,800Q640,800 640,800Q640,800 640,800L640,520L160,520L160,800Q160,800 160,800Q160,800 160,800ZM720,520L800,520Q800,520 800,520Q800,520 800,520L800,240L320,240L320,360L640,360Q673,360 696.5,383.5Q720,407 720,440L720,520Z"/> +</vector> diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml index eea3de8e30ca..64f71c713d1c 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml @@ -147,6 +147,14 @@ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_new_window" android:drawableTint="?androidprv:attr/materialColorOnSurface" style="@style/DesktopModeHandleMenuActionButton" /> + + <Button + android:id="@+id/manage_windows_button" + android:contentDescription="@string/manage_windows_text" + android:text="@string/manage_windows_text" + android:drawableStart="@drawable/desktop_mode_ic_handle_menu_manage_windows" + android:drawableTint="?androidprv:attr/materialColorOnSurface" + style="@style/DesktopModeHandleMenuActionButton" /> </LinearLayout> <LinearLayout diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 755e0d5f742d..c76c47041ebf 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -504,9 +504,9 @@ <!-- The width of the handle menu in desktop mode. --> <dimen name="desktop_mode_handle_menu_width">216dp</dimen> - <!-- The maximum height of the handle menu in desktop mode. Four pills (52dp each) plus 2dp - spacing between them plus 4dp top padding. --> - <dimen name="desktop_mode_handle_menu_height">270dp</dimen> + <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each, + additional actions pill 156dp, plus 2dp spacing between them plus 4dp top padding. --> + <dimen name="desktop_mode_handle_menu_height">322dp</dimen> <!-- The elevation set on the handle menu pills. --> <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen> @@ -520,6 +520,9 @@ <!-- The maximum height of the handle menu's "New Window" button in desktop mode. --> <dimen name="desktop_mode_handle_menu_new_window_height">52dp</dimen> + <!-- The maximum height of the handle menu's "Manage Windows" button in desktop mode. --> + <dimen name="desktop_mode_handle_menu_manage_windows_height">52dp</dimen> + <!-- The maximum height of the handle menu's "Screenshot" button in desktop mode. --> <dimen name="desktop_mode_handle_menu_screenshot_height">52dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index a353db72b914..a6da421dbbb9 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -294,6 +294,8 @@ <string name="open_in_browser_text">Open in browser</string> <!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] --> <string name="new_window_text">New Window</string> + <!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] --> + <string name="manage_windows_text">Manage Windows</string> <!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] --> <string name="close_text">Close</string> <!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] --> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt new file mode 100644 index 000000000000..79becb0a2e20 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt @@ -0,0 +1,173 @@ +/* + * 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.wm.shell.shared.desktopmode +import android.annotation.ColorInt +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.RoundRectShape +import android.util.TypedValue +import android.view.MotionEvent.ACTION_OUTSIDE +import android.view.SurfaceView +import android.view.ViewGroup.MarginLayoutParams +import android.widget.LinearLayout +import android.window.TaskSnapshot + +/** + * View for the All Windows menu option, used by both Desktop Windowing and Taskbar. + * The menu displays icons of all open instances of an app. Clicking the icon should launch + * the instance, which will be performed by the child class. + */ +abstract class ManageWindowsViewContainer( + val context: Context, + @ColorInt private val menuBackgroundColor: Int +) { + lateinit var menuView: ManageWindowsView + + /** Creates the base menu view and fills it with icon views. */ + fun show(snapshotList: List<Pair<Int, TaskSnapshot>>, + onIconClickListener: ((Int) -> Unit), + onOutsideClickListener: (() -> Unit)): ManageWindowsView { + menuView = ManageWindowsView(context, menuBackgroundColor).apply { + this.onOutsideClickListener = onOutsideClickListener + this.onIconClickListener = onIconClickListener + this.generateIconViews(snapshotList) + } + addToContainer(menuView) + return menuView + } + + /** Adds the menu view to the container responsible for displaying it. */ + abstract fun addToContainer(menuView: ManageWindowsView) + + /** Dispose of the menu, perform needed cleanup. */ + abstract fun close() + + companion object { + const val MANAGE_WINDOWS_MINIMUM_INSTANCES = 2 + } + + class ManageWindowsView( + private val context: Context, + menuBackgroundColor: Int + ) { + val rootView: LinearLayout = LinearLayout(context) + var menuHeight = 0 + var menuWidth = 0 + var onIconClickListener: ((Int) -> Unit)? = null + var onOutsideClickListener: (() -> Unit)? = null + + init { + rootView.orientation = LinearLayout.VERTICAL + val menuBackground = ShapeDrawable() + val menuRadius = getDimensionPixelSize(MENU_RADIUS_DP) + menuBackground.shape = RoundRectShape( + FloatArray(8) { menuRadius }, + null, + null + ) + menuBackground.paint.color = menuBackgroundColor + rootView.background = menuBackground + rootView.elevation = getDimensionPixelSize(MENU_ELEVATION_DP) + rootView.setOnTouchListener { _, event -> + if (event.actionMasked == ACTION_OUTSIDE) { + onOutsideClickListener?.invoke() + } + return@setOnTouchListener true + } + } + + private fun getDimensionPixelSize(sizeDp: Float): Float { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + sizeDp, context.resources.displayMetrics) + } + + fun generateIconViews( + snapshotList: List<Pair<Int, TaskSnapshot>> + ) { + menuWidth = 0 + menuHeight = 0 + rootView.removeAllViews() + val instanceIconHeight = getDimensionPixelSize(ICON_HEIGHT_DP) + val instanceIconWidth = getDimensionPixelSize(ICON_WIDTH_DP) + val iconRadius = getDimensionPixelSize(ICON_RADIUS_DP) + val iconMargin = getDimensionPixelSize(ICON_MARGIN_DP) + var rowLayout: LinearLayout? = null + // Add each icon to the menu, adding a new row when needed. + for ((iconCount, taskInfoSnapshotPair) in snapshotList.withIndex()) { + val taskId = taskInfoSnapshotPair.first + val snapshot = taskInfoSnapshotPair.second + // Once a row is filled, make a new row and increase the menu height. + if (iconCount % MENU_MAX_ICONS_PER_ROW == 0) { + rowLayout = LinearLayout(context) + rowLayout.orientation = LinearLayout.HORIZONTAL + rootView.addView(rowLayout) + menuHeight += (instanceIconHeight + iconMargin).toInt() + } + val snapshotBitmap = Bitmap.wrapHardwareBuffer( + snapshot.hardwareBuffer, + snapshot.colorSpace + ) + val scaledSnapshotBitmap = snapshotBitmap?.let { + Bitmap.createScaledBitmap( + it, instanceIconWidth.toInt(), instanceIconHeight.toInt(), true /* filter */ + ) + } + val appSnapshotButton = SurfaceView(context) + appSnapshotButton.cornerRadius = iconRadius + appSnapshotButton.setZOrderOnTop(true) + appSnapshotButton.setOnClickListener { + onIconClickListener?.invoke(taskId) + } + val lp = MarginLayoutParams( + instanceIconWidth.toInt(), instanceIconHeight.toInt() + ) + lp.apply { + marginStart = iconMargin.toInt() + topMargin = iconMargin.toInt() + } + appSnapshotButton.layoutParams = lp + // If we haven't already reached one full row, increment width. + if (iconCount < MENU_MAX_ICONS_PER_ROW) { + menuWidth += (instanceIconWidth + iconMargin).toInt() + } + rowLayout?.addView(appSnapshotButton) + appSnapshotButton.requestLayout() + rowLayout?.post { + appSnapshotButton.holder.surface + .attachAndQueueBufferWithColorSpace( + scaledSnapshotBitmap?.hardwareBuffer, + scaledSnapshotBitmap?.colorSpace + ) + } + } + // Add margin again for the right/bottom of the menu. + menuWidth += iconMargin.toInt() + menuHeight += iconMargin.toInt() + } + + companion object { + private const val MENU_RADIUS_DP = 26f + private const val ICON_WIDTH_DP = 204f + private const val ICON_HEIGHT_DP = 127.5f + private const val ICON_RADIUS_DP = 16f + private const val ICON_MARGIN_DP = 16f + private const val MENU_ELEVATION_DP = 1f + private const val MENU_MAX_ICONS_PER_ROW = 3 + } + } +} 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 1d16980c617d..7e9625361efc 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 @@ -1077,45 +1077,47 @@ class DesktopTasksController( request.triggerTask != null } + /** Open an existing instance of an app. */ + fun openInstance( + callingTask: RunningTaskInfo, + requestedTaskId: Int + ) { + val wct = WindowContainerTransaction() + val options = createNewWindowOptions(callingTask) + if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) { + wct.startTask(requestedTaskId, options.toBundle()) + transitions.startTransition(TRANSIT_OPEN, wct, null) + } else { + val splitPosition = splitScreenController.determineNewInstancePosition(callingTask) + splitScreenController.startTask(requestedTaskId, splitPosition, + options.toBundle(), null /* hideTaskToken */) + } + } + + /** Create an Intent to open a new window of a task. */ fun openNewWindow( - taskInfo: RunningTaskInfo + callingTaskInfo: RunningTaskInfo ) { // TODO(b/337915660): Add a transition handler for these; animations // need updates in some cases. - val newTaskWindowingMode = when { - taskInfo.isFreeform -> { - WINDOWING_MODE_FREEFORM - } - taskInfo.isFullscreen || taskInfo.isMultiWindow -> { - WINDOWING_MODE_MULTI_WINDOW - } - else -> { - error("Invalid windowing mode: ${taskInfo.windowingMode}") - } - } - - val baseActivity = taskInfo.baseActivity ?: return + val baseActivity = callingTaskInfo.baseActivity ?: return val fillIn: Intent = context.packageManager .getLaunchIntentForPackage( baseActivity.packageName ) ?: return fillIn .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - val options = - ActivityOptions.makeBasic().apply { - launchWindowingMode = newTaskWindowingMode - pendingIntentBackgroundActivityStartMode = - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS - } val launchIntent = PendingIntent.getActivity( context, /* requestCode= */ 0, fillIn, PendingIntent.FLAG_IMMUTABLE ) - when (newTaskWindowingMode) { + val options = createNewWindowOptions(callingTaskInfo) + when (options.launchWindowingMode) { WINDOWING_MODE_MULTI_WINDOW -> { - val splitPosition = splitScreenController.determineNewInstancePosition(taskInfo) + val splitPosition = splitScreenController + .determineNewInstancePosition(callingTaskInfo) splitScreenController.startIntent( launchIntent, context.userId, fillIn, splitPosition, options.toBundle(), null /* hideTaskToken */ @@ -1130,6 +1132,25 @@ class DesktopTasksController( } } + private fun createNewWindowOptions(callingTask: RunningTaskInfo): ActivityOptions { + val newTaskWindowingMode = when { + callingTask.isFreeform -> { + WINDOWING_MODE_FREEFORM + } + callingTask.isFullscreen || callingTask.isMultiWindow -> { + WINDOWING_MODE_MULTI_WINDOW + } + else -> { + error("Invalid windowing mode: ${callingTask.windowingMode}") + } + } + return ActivityOptions.makeBasic().apply { + launchWindowingMode = newTaskWindowingMode + pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + } + } + /** * Handles the case where a freeform task is launched from recents. * 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 cf02fb5fde8e..22e8dc186e9b 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 @@ -26,7 +26,6 @@ import static android.view.DragEvent.ACTION_DROP; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static android.view.WindowManager.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; @@ -247,9 +246,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll R.layout.global_drop_target, null); rootView.setOnDragListener(this); rootView.setVisibility(View.INVISIBLE); - DragLayout dragLayout = new DragLayout(context, mSplitScreen, mIconProvider); - rootView.addView(dragLayout, - new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + DragLayoutProvider dragLayout = new DragLayout(context, mSplitScreen, mIconProvider); + dragLayout.addDraggingView(rootView); try { wm.addView(rootView, layoutParams); addDisplayDropTarget(displayId, context, wm, rootView, dragLayout); @@ -261,7 +259,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll @VisibleForTesting void addDisplayDropTarget(int displayId, Context context, WindowManager wm, - FrameLayout rootView, DragLayout dragLayout) { + FrameLayout rootView, DragLayoutProvider dragLayout) { mDisplayDropTargets.put(displayId, new PerDisplay(displayId, context, wm, rootView, dragLayout)); } @@ -564,7 +562,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll final Context context; final WindowManager wm; final FrameLayout rootView; - final DragLayout dragLayout; + final DragLayoutProvider dragLayout; // Tracks whether the window has fully drawn since it was last made visible boolean hasDrawn; @@ -575,7 +573,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll // The active drag session DragSession dragSession; - PerDisplay(int dispId, Context c, WindowManager w, FrameLayout rv, DragLayout dl) { + PerDisplay(int dispId, Context c, WindowManager w, FrameLayout rv, DragLayoutProvider dl) { displayId = dispId; context = c; wm = w; 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 3fecbe7fff74..dfa24370590a 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 @@ -23,10 +23,10 @@ import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -47,6 +47,7 @@ import android.graphics.Region; import android.graphics.drawable.Drawable; import android.view.DragEvent; import android.view.SurfaceControl; +import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowInsets.Type; @@ -66,13 +67,13 @@ import com.android.wm.shell.shared.animation.Interpolators; import com.android.wm.shell.splitscreen.SplitScreenController; import java.io.PrintWriter; -import java.util.ArrayList; +import java.util.List; /** * Coordinates the visible drop targets for the current drag within a single display. */ public class DragLayout extends LinearLayout - implements ViewTreeObserver.OnComputeInternalInsetsListener { + implements ViewTreeObserver.OnComputeInternalInsetsListener, DragLayoutProvider { // While dragging the status bar is hidden. private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS @@ -80,7 +81,7 @@ public class DragLayout extends LinearLayout | StatusBarManager.DISABLE_CLOCK | StatusBarManager.DISABLE_SYSTEM_INFO; - private final DragAndDropPolicy mPolicy; + private final DropTarget mPolicy; private final SplitScreenController mSplitScreenController; private final IconProvider mIconProvider; private final StatusBarManager mStatusBarManager; @@ -91,7 +92,7 @@ public class DragLayout extends LinearLayout // Whether the device is currently in left/right split mode private boolean mIsLeftRightSplit; - private DragAndDropPolicy.Target mCurrentTarget = null; + private SplitDragPolicy.Target mCurrentTarget = null; private DropZoneView mDropZoneView1; private DropZoneView mDropZoneView2; @@ -113,7 +114,7 @@ public class DragLayout extends LinearLayout super(context); mSplitScreenController = splitScreenController; mIconProvider = iconProvider; - mPolicy = new DragAndDropPolicy(context, splitScreenController); + mPolicy = new SplitDragPolicy(context, splitScreenController); mStatusBarManager = context.getSystemService(StatusBarManager.class); mLastConfiguration.setTo(context.getResources().getConfiguration()); @@ -387,6 +388,13 @@ public class DragLayout extends LinearLayout recomputeDropTargets(); } + @NonNull + @Override + public void addDraggingView(ViewGroup rootView) { + // TODO(b/349828130) We need to separate out view + logic here + rootView.addView(this, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + } + /** * Recalculates the drop targets based on the current policy. */ @@ -394,9 +402,9 @@ public class DragLayout extends LinearLayout if (!mIsShowing) { return; } - final ArrayList<DragAndDropPolicy.Target> targets = mPolicy.getTargets(mInsets); + final List<SplitDragPolicy.Target> targets = mPolicy.getTargets(mInsets); for (int i = 0; i < targets.size(); i++) { - final DragAndDropPolicy.Target target = targets.get(i); + final SplitDragPolicy.Target target = targets.get(i); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Add target: %s", target); // Inset the draw region by a little bit target.drawRegion.inset(mDisplayMargin, mDisplayMargin); @@ -419,7 +427,7 @@ public class DragLayout extends LinearLayout } // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the // visibility of the current region - DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(x, y); + SplitDragPolicy.Target target = mPolicy.getTargetAtLocation(x, y); if (mCurrentTarget != target) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target); if (target == null) { @@ -493,7 +501,7 @@ public class DragLayout extends LinearLayout mHasDropped = true; // Process the drop - mPolicy.handleDrop(mCurrentTarget, hideTaskToken); + mPolicy.onDropped(mCurrentTarget, hideTaskToken); // Start animating the drop UI out with the drag surface hide(event, dropCompleteCallback); @@ -576,7 +584,7 @@ public class DragLayout extends LinearLayout } } - private void animateHighlight(DragAndDropPolicy.Target target) { + private void animateHighlight(SplitDragPolicy.Target target) { if (target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP) { mDropZoneView1.setShowingHighlight(true); mDropZoneView2.setShowingHighlight(false); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayoutProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayoutProvider.kt new file mode 100644 index 000000000000..3d408242f5f8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayoutProvider.kt @@ -0,0 +1,80 @@ +/* + * 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.wm.shell.draganddrop + +import android.content.res.Configuration +import android.view.DragEvent +import android.view.SurfaceControl +import android.view.ViewGroup +import android.window.WindowContainerToken +import com.android.internal.logging.InstanceId +import java.io.PrintWriter + +/** Interface to be implemented by any controllers providing a layout for DragAndDrop in Shell */ +interface DragLayoutProvider { + /** + * Updates the drag layout based on the given drag session. + */ + fun updateSession(session: DragSession) + /** + * Called when a new drag is started. + */ + fun prepare(session: DragSession, loggerSessionId: InstanceId?) + + /** + * Shows the drag layout. + */ + fun show() + + /** + * Updates the visible drop target as the user drags. + */ + fun update(event: DragEvent?) + + /** + * Hides the drag layout and animates out the visible drop targets. + */ + fun hide(event: DragEvent?, hideCompleteCallback: Runnable?) + + /** + * Whether target has already been dropped or not + */ + fun hasDropped(): Boolean + + /** + * Handles the drop onto a target and animates out the visible drop targets. + */ + fun drop( + event: DragEvent?, dragSurface: SurfaceControl, + hideTaskToken: WindowContainerToken?, dropCompleteCallback: Runnable? + ): Boolean + + /** + * Dumps information about this drag layout. + */ + fun dump(pw: PrintWriter, prefix: String?) + + /** + * @return a View which will be added to the global root view for drag and drop + */ + fun addDraggingView(viewGroup: ViewGroup) + + /** + * Called when the configuration changes. + */ + fun onConfigChanged(newConfig: Configuration?) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.kt new file mode 100644 index 000000000000..122a105dbf8d --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.kt @@ -0,0 +1,56 @@ +/* + * 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.wm.shell.draganddrop + +import android.graphics.Insets +import android.window.WindowContainerToken +import com.android.internal.logging.InstanceId + +/** + * Interface to be implemented by classes which want to provide drop targets + * for DragAndDrop in Shell + */ +interface DropTarget { + // TODO(b/349828130) Delete after flexible split launches + /** + * Called at the start of a Drag, before input events are processed. + */ + fun start(dragSession: DragSession, logSessionId: InstanceId) + /** + * @return [SplitDragPolicy.Target] corresponding to the given coords in display bounds. + */ + fun getTargetAtLocation(x: Int, y: Int) : SplitDragPolicy.Target + /** + * @return total number of drop targets for the current drag session. + */ + fun getNumTargets() : Int + // TODO(b/349828130) + + /** + * @return [List<SplitDragPolicy.Target>] to show for the current drag session. + */ + fun getTargets(insets: Insets) : List<SplitDragPolicy.Target> + /** + * Called when user is hovering Drag object over the given Target + */ + fun onHoveringOver(target: SplitDragPolicy.Target) {} + /** + * Called when the user has dropped the provided target (need not be the same target as + * [onHoveringOver]) + */ + fun onDropped(target: SplitDragPolicy.Target, hideTaskToken: WindowContainerToken) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java index 6fec0c1c20bc..2a19d6512b56 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java @@ -32,11 +32,11 @@ import static android.content.Intent.EXTRA_USER; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_FULLSCREEN; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP; import static com.android.wm.shell.shared.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -80,9 +80,9 @@ import java.util.ArrayList; /** * The policy for handling drag and drop operations to shell. */ -public class DragAndDropPolicy { +public class SplitDragPolicy implements DropTarget { - private static final String TAG = DragAndDropPolicy.class.getSimpleName(); + private static final String TAG = SplitDragPolicy.class.getSimpleName(); private final Context mContext; // Used only for launching a fullscreen task (or as a fallback if there is no split starter) @@ -90,18 +90,18 @@ public class DragAndDropPolicy { // Used for launching tasks into splitscreen private final Starter mSplitscreenStarter; private final SplitScreenController mSplitScreen; - private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>(); + private final ArrayList<SplitDragPolicy.Target> mTargets = new ArrayList<>(); private final RectF mDisallowHitRegion = new RectF(); private InstanceId mLoggerSessionId; private DragSession mSession; - public DragAndDropPolicy(Context context, SplitScreenController splitScreen) { + public SplitDragPolicy(Context context, SplitScreenController splitScreen) { this(context, splitScreen, new DefaultStarter(context)); } @VisibleForTesting - DragAndDropPolicy(Context context, SplitScreenController splitScreen, + SplitDragPolicy(Context context, SplitScreenController splitScreen, Starter fullscreenStarter) { mContext = context; mSplitScreen = splitScreen; @@ -112,7 +112,7 @@ public class DragAndDropPolicy { /** * Starts a new drag session with the given initial drag data. */ - void start(DragSession session, InstanceId loggerSessionId) { + public void start(DragSession session, InstanceId loggerSessionId) { mLoggerSessionId = loggerSessionId; mSession = session; RectF disallowHitRegion = mSession.appData != null @@ -128,7 +128,8 @@ public class DragAndDropPolicy { /** * Returns the number of targets. */ - int getNumTargets() { + @Override + public int getNumTargets() { return mTargets.size(); } @@ -136,7 +137,8 @@ public class DragAndDropPolicy { * Returns the target's regions based on the current state of the device and display. */ @NonNull - ArrayList<Target> getTargets(Insets insets) { + @Override + public ArrayList<Target> getTargets(@NonNull Insets insets) { mTargets.clear(); if (mSession == null) { // Return early if this isn't an app drag @@ -222,12 +224,12 @@ public class DragAndDropPolicy { * Returns the target at the given position based on the targets previously calculated. */ @Nullable - Target getTargetAtLocation(int x, int y) { + public Target getTargetAtLocation(int x, int y) { if (mDisallowHitRegion.contains(x, y)) { return null; } for (int i = mTargets.size() - 1; i >= 0; i--) { - DragAndDropPolicy.Target t = mTargets.get(i); + SplitDragPolicy.Target t = mTargets.get(i); if (t.hitRegion.contains(x, y)) { return t; } @@ -241,7 +243,7 @@ public class DragAndDropPolicy { * container transaction if possible. */ @VisibleForTesting - void handleDrop(Target target, @Nullable WindowContainerToken hideTaskToken) { + public void onDropped(Target target, @Nullable WindowContainerToken hideTaskToken) { if (target == null || !mTargets.contains(target)) { return; } @@ -419,8 +421,9 @@ public class DragAndDropPolicy { /** * Represents a drop target. + * TODO(b/349828130): Move this into {@link DropTarget} */ - static class Target { + public static class Target { static final int TYPE_FULLSCREEN = 0; static final int TYPE_SPLIT_LEFT = 1; static final int TYPE_SPLIT_TOP = 2; 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 793e2aa757a3..87b661d340ed 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 @@ -92,7 +92,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; -import com.android.wm.shell.draganddrop.DragAndDropPolicy; +import com.android.wm.shell.draganddrop.SplitDragPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.shared.TransactionPool; @@ -121,7 +121,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * @see StageCoordinator */ // TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen. -public class SplitScreenController implements DragAndDropPolicy.Starter, +public class SplitScreenController implements SplitDragPolicy.Starter, RemoteCallable<SplitScreenController>, KeyguardChangeListener { private static final String TAG = SplitScreenController.class.getSimpleName(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt new file mode 100644 index 000000000000..13a805aef0f1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt @@ -0,0 +1,109 @@ +/* + * 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.wm.shell.windowdecor + +import android.app.ActivityManager.RunningTaskInfo +import android.content.Context +import android.graphics.Point +import android.graphics.Rect +import android.view.WindowManager +import android.window.TaskSnapshot +import androidx.compose.ui.graphics.toArgb +import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer +import com.android.wm.shell.shared.split.SplitScreenConstants +import com.android.wm.shell.splitscreen.SplitScreenController +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer +import com.android.wm.shell.windowdecor.common.DecorThemeUtil +import com.android.wm.shell.windowdecor.extension.isFullscreen +import com.android.wm.shell.windowdecor.extension.isMultiWindow + +/** + * Implementation of [ManageWindowsViewContainer] meant to be used in desktop header and app + * handle. + */ +class DesktopHandleManageWindowsMenu( + private val callerTaskInfo: RunningTaskInfo, + private val splitScreenController: SplitScreenController, + private val captionX: Int, + private val captionWidth: Int, + private val windowManagerWrapper: WindowManagerWrapper, + context: Context, + snapshotList: List<Pair<Int, TaskSnapshot>>, + onIconClickListener: ((Int) -> Unit), + onOutsideClickListener: (() -> Unit) +) : ManageWindowsViewContainer( + context, + DecorThemeUtil(context).getColorScheme(callerTaskInfo).background.toArgb() +) { + private var menuViewContainer: AdditionalViewContainer? = null + + init { + show(snapshotList, onIconClickListener, onOutsideClickListener) + } + + override fun close() { + menuViewContainer?.releaseView() + } + + private fun calculateMenuPosition(): Point { + val position = Point() + val nonFreeformX = (captionX + (captionWidth / 2) - (menuView.menuWidth / 2)) + when { + callerTaskInfo.isFreeform -> { + val taskBounds = callerTaskInfo.getConfiguration().windowConfiguration.bounds + position.set(taskBounds.left, taskBounds.top) + } + callerTaskInfo.isFullscreen -> { + position.set(nonFreeformX, 0) + } + callerTaskInfo.isMultiWindow -> { + val splitPosition = splitScreenController.getSplitPosition(callerTaskInfo.taskId) + val leftOrTopStageBounds = Rect() + val rightOrBottomStageBounds = Rect() + splitScreenController.getStageBounds(leftOrTopStageBounds, rightOrBottomStageBounds) + // TODO(b/343561161): This needs to be calculated differently if the task is in + // top/bottom split. + when (splitPosition) { + SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> { + position.set(leftOrTopStageBounds.width() + nonFreeformX, /* y= */ 0) + } + + SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> { + position.set(nonFreeformX, /* y= */ 0) + } + } + } + } + return position + } + + override fun addToContainer(menuView: ManageWindowsView) { + val menuPosition = calculateMenuPosition() + menuViewContainer = AdditionalSystemViewContainer( + windowManagerWrapper, + callerTaskInfo.taskId, + menuPosition.x, + menuPosition.y, + menuView.menuWidth, + menuView.menuHeight, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, + menuView.rootView + ) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt new file mode 100644 index 000000000000..05391a8343a5 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt @@ -0,0 +1,104 @@ +/* + * 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.wm.shell.windowdecor + +import android.app.ActivityManager.RunningTaskInfo +import android.content.Context +import android.graphics.PixelFormat +import android.graphics.Point +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.WindowManager +import android.view.WindowlessWindowManager +import android.window.TaskConstants +import android.window.TaskSnapshot +import androidx.compose.ui.graphics.toArgb +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer +import com.android.wm.shell.windowdecor.common.DecorThemeUtil +import java.util.function.Supplier + +/** + * Implementation of [ManageWindowsViewContainer] meant to be used in desktop header and app + * handle. + */ +class DesktopHeaderManageWindowsMenu( + private val callerTaskInfo: RunningTaskInfo, + private val displayController: DisplayController, + private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, + context: Context, + private val surfaceControlBuilderSupplier: Supplier<SurfaceControl.Builder>, + private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>, + snapshotList: List<Pair<Int, TaskSnapshot>>, + onIconClickListener: ((Int) -> Unit), + onOutsideClickListener: (() -> Unit) +) : ManageWindowsViewContainer( + context, + DecorThemeUtil(context).getColorScheme(callerTaskInfo).background.toArgb() +) { + private var menuViewContainer: AdditionalViewContainer? = null + + init { + show(snapshotList, onIconClickListener, onOutsideClickListener) + } + + override fun close() { + menuViewContainer?.releaseView() + } + + override fun addToContainer(menuView: ManageWindowsView) { + val taskBounds = callerTaskInfo.getConfiguration().windowConfiguration.bounds + val menuPosition = Point(taskBounds.left, taskBounds.top) + val builder = surfaceControlBuilderSupplier.get() + rootTdaOrganizer.attachToDisplayArea(callerTaskInfo.displayId, builder) + val leash = builder + .setName("Manage Windows Menu") + .setContainerLayer() + .build() + val lp = WindowManager.LayoutParams( + menuView.menuWidth, menuView.menuHeight, + WindowManager.LayoutParams.TYPE_APPLICATION, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + PixelFormat.TRANSPARENT + ) + val windowManager = WindowlessWindowManager( + callerTaskInfo.configuration, + leash, + null // HostInputToken + ) + val viewHost = SurfaceControlViewHost( + context, + displayController.getDisplay(callerTaskInfo.displayId), windowManager, + "MaximizeMenu" + ) + menuView.let { viewHost.setView(it.rootView, lp) } + val t = surfaceControlTransactionSupplier.get() + t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU) + .setPosition(leash, menuPosition.x.toFloat(), menuPosition.y.toFloat()) + .show(leash) + t.apply() + menuViewContainer = AdditionalViewHostViewContainer( + leash, viewHost, + surfaceControlTransactionSupplier + ) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 7692bd7b57e4..2519ce4e6571 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -39,6 +39,7 @@ import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.Indica import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; +import static com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer.MANAGE_WINDOWS_MINIMUM_INSTANCES; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -48,6 +49,8 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; +import android.app.IActivityManager; +import android.app.IActivityTaskManager; import android.content.Context; import android.content.Intent; import android.graphics.Point; @@ -77,6 +80,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.View; import android.widget.Toast; +import android.window.TaskSnapshot; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -125,9 +129,12 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; +import kotlin.Pair; import kotlin.Unit; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.function.Supplier; @@ -586,6 +593,61 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopTasksController.openNewWindow(decoration.mTaskInfo); } + private void onManageWindows(DesktopModeWindowDecoration decoration) { + if (decoration == null) { + return; + } + decoration.closeHandleMenu(); + decoration.createManageWindowsMenu(getTaskSnapshots(decoration.mTaskInfo), + /* onIconClickListener= */(Integer requestedTaskId) -> { + decoration.closeManageWindowsMenu(); + mDesktopTasksController.openInstance(decoration.mTaskInfo, requestedTaskId); + return Unit.INSTANCE; + }); + } + + private ArrayList<Pair<Integer, TaskSnapshot>> getTaskSnapshots( + @NonNull RunningTaskInfo callerTaskInfo + ) { + final ArrayList<Pair<Integer, TaskSnapshot>> snapshotList = new ArrayList<>(); + final IActivityManager activityManager = ActivityManager.getService(); + final IActivityTaskManager activityTaskManagerService = ActivityTaskManager.getService(); + final List<ActivityManager.RecentTaskInfo> recentTasks; + try { + recentTasks = mActivityTaskManager.getRecentTasks( + Integer.MAX_VALUE, + ActivityManager.RECENT_WITH_EXCLUDED, + activityManager.getCurrentUser().id); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + final String callerPackageName = callerTaskInfo.baseActivity.getPackageName(); + for (ActivityManager.RecentTaskInfo info : recentTasks) { + if (info.taskId == callerTaskInfo.taskId || info.baseActivity == null) continue; + final String infoPackageName = info.baseActivity.getPackageName(); + if (!infoPackageName.equals(callerPackageName)) { + continue; + } + if (info.baseActivity != null) { + if (callerPackageName.equals(infoPackageName)) { + // TODO(b/337903443): Fix this returning null for freeform tasks. + try { + TaskSnapshot screenshot = activityTaskManagerService + .getTaskSnapshot(info.taskId, false); + if (screenshot == null) { + screenshot = activityTaskManagerService + .takeTaskSnapshot(info.taskId, false); + } + snapshotList.add(new Pair(info.taskId, screenshot)); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + } + return snapshotList; + } + private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, View.OnGenericMotionListener, DragDetector.MotionEventHandler { @@ -642,7 +704,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (id == R.id.caption_handle || id == R.id.open_menu_button) { if (!decoration.isHandleMenuActive()) { moveTaskToFront(decoration.mTaskInfo); - decoration.createHandleMenu(); + decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo) + >= MANAGE_WINDOWS_MINIMUM_INSTANCES); } } else if (id == R.id.maximize_window) { // TODO(b/346441962): move click detection logic into the decor's @@ -1350,6 +1413,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { onNewWindow(taskInfo.taskId); return Unit.INSTANCE; }); + windowDecoration.setManageWindowsClickListener(() -> { + onManageWindows(windowDecoration); + return Unit.INSTANCE; + }); windowDecoration.setCaptionListeners( touchEventListener, touchEventListener, touchEventListener, touchEventListener); windowDecoration.setExclusionRegionListener(mExclusionRegionListener); @@ -1441,6 +1508,29 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } + /** + * Gets the number of instances of a task running, not including the specified task itself. + */ + private int checkNumberOfOtherInstances(@NonNull RunningTaskInfo info) { + // TODO(b/336289597): Rather than returning number of instances, return a list of valid + // instances, then refer to the list's size and reuse the list for Manage Windows menu. + final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService(); + final IActivityManager activityManager = ActivityManager.getService(); + try { + return activityTaskManager.getRecentTasks(Integer.MAX_VALUE, + ActivityManager.RECENT_WITH_EXCLUDED, + activityManager.getCurrentUserId()).getList().stream().filter( + recentTaskInfo -> (recentTaskInfo.taskId != info.taskId + && recentTaskInfo.baseActivity != null + && recentTaskInfo.baseActivity.getPackageName() + .equals(info.baseActivity.getPackageName()) + ) + ).toList().size(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + static class InputMonitorFactory { InputMonitor create(InputManager inputManager, int displayId) { return inputManager.monitorGestureInput("caption-touch", displayId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 1409d3011395..5d16d972a0f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -64,6 +64,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.WindowManager; import android.widget.ImageButton; +import android.window.TaskSnapshot; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; @@ -86,6 +87,7 @@ import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.desktopmode.DesktopModeFlags; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; +import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder; @@ -93,9 +95,12 @@ import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder; import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; +import kotlin.Pair; import kotlin.Unit; import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; +import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; @@ -131,6 +136,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private Function0<Unit> mOnToFullscreenClickListener; private Function0<Unit> mOnToSplitscreenClickListener; private Function0<Unit> mOnNewWindowClickListener; + private Function0<Unit> mOnManageWindowsClickListener; private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; private DragDetector mDragDetector; @@ -140,6 +146,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final Point mPositionInParent = new Point(); private HandleMenu mHandleMenu; + private boolean mMinimumInstancesFound; + private ManageWindowsViewContainer mManageWindowsMenu; private MaximizeMenu mMaximizeMenu; @@ -285,6 +293,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mOnNewWindowClickListener = listener; } + /** + * Registers a listener to be called when the decoration's manage windows action is + * triggered. + */ + void setManageWindowsClickListener(Function0<Unit> listener) { + mOnManageWindowsClickListener = listener; + } + void setCaptionListeners( View.OnClickListener onCaptionButtonClickListener, View.OnTouchListener onCaptionTouchListener, @@ -941,9 +957,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin /** * Updates app info and creates and displays handle menu window. */ - void createHandleMenu() { + void createHandleMenu(boolean minimumInstancesFound) { // Requests assist content. When content is received, calls {@link #onAssistContentReceived} // which sets app info and creates the handle menu. + mMinimumInstancesFound = minimumInstancesFound; mAssistContentRequester.requestAssistContent( mTaskInfo.taskId, this::onAssistContentReceived); } @@ -956,8 +973,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mWebUri = assistContent == null ? null : assistContent.getWebUri(); loadAppInfoIfNeeded(); updateGenericLink(); - - // Create and display handle menu + final boolean supportsMultiInstance = mMultiInstanceHelper + .supportsMultiInstanceSplit(mTaskInfo.baseActivity); + final boolean shouldShowManageWindowsButton = supportsMultiInstance + && mMinimumInstancesFound; mHandleMenu = mHandleMenuFactory.create( this, mWindowManagerWrapper, @@ -966,9 +985,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mAppName, mSplitScreenController, DesktopModeStatus.canEnterDesktopMode(mContext), - Flags.enableDesktopWindowingMultiInstanceFeatures() - && mMultiInstanceHelper - .supportsMultiInstanceSplit(mTaskInfo.baseActivity), + supportsMultiInstance, + shouldShowManageWindowsButton, getBrowserLink(), mResult.mCaptionWidth, mResult.mCaptionHeight, @@ -984,6 +1002,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin /* onToFullscreenClickListener= */ mOnToFullscreenClickListener, /* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener, /* onNewWindowClickListener= */ mOnNewWindowClickListener, + /* onManageWindowsClickListener= */ mOnManageWindowsClickListener, /* openInBrowserClickListener= */ (uri) -> { mOpenInBrowserClickListener.accept(uri); onCapturedLinkExpired(); @@ -998,6 +1017,47 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return Unit.INSTANCE; } ); + mMinimumInstancesFound = false; + } + + void createManageWindowsMenu(@NonNull List<Pair<Integer, TaskSnapshot>> snapshotList, + @NonNull Function1<Integer, Unit> onIconClickListener + ) { + if (mTaskInfo.isFreeform()) { + mManageWindowsMenu = new DesktopHeaderManageWindowsMenu( + mTaskInfo, + mDisplayController, + mRootTaskDisplayAreaOrganizer, + mContext, + mSurfaceControlBuilderSupplier, + mSurfaceControlTransactionSupplier, + snapshotList, + onIconClickListener, + /* onOutsideClickListener= */ () -> { + closeManageWindowsMenu(); + return Unit.INSTANCE; + } + ); + } else { + mManageWindowsMenu = new DesktopHandleManageWindowsMenu( + mTaskInfo, + mSplitScreenController, + getCaptionX(), + mResult.mCaptionWidth, + mWindowManagerWrapper, + mContext, + snapshotList, + onIconClickListener, + /* onOutsideClickListener= */ () -> { + closeManageWindowsMenu(); + return Unit.INSTANCE; + } + ); + } + } + + void closeManageWindowsMenu() { + mManageWindowsMenu.close(); } private void updateGenericLink() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index 748046ebd08d..3d00a445d9e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -71,6 +71,7 @@ class HandleMenu( private val splitScreenController: SplitScreenController, private val shouldShowWindowingPill: Boolean, private val shouldShowNewWindowButton: Boolean, + private val shouldShowManageWindowsButton: Boolean, private val openInBrowserLink: Uri?, private val captionWidth: Int, private val captionHeight: Int, @@ -119,6 +120,7 @@ class HandleMenu( onToFullscreenClickListener: () -> Unit, onToSplitScreenClickListener: () -> Unit, onNewWindowClickListener: () -> Unit, + onManageWindowsClickListener: () -> Unit, openInBrowserClickListener: (Uri) -> Unit, onCloseMenuClickListener: () -> Unit, onOutsideTouchListener: () -> Unit, @@ -133,6 +135,7 @@ class HandleMenu( onToFullscreenClickListener = onToFullscreenClickListener, onToSplitScreenClickListener = onToSplitScreenClickListener, onNewWindowClickListener = onNewWindowClickListener, + onManageWindowsClickListener = onManageWindowsClickListener, openInBrowserClickListener = openInBrowserClickListener, onCloseMenuClickListener = onCloseMenuClickListener, onOutsideTouchListener = onOutsideTouchListener, @@ -150,6 +153,7 @@ class HandleMenu( onToFullscreenClickListener: () -> Unit, onToSplitScreenClickListener: () -> Unit, onNewWindowClickListener: () -> Unit, + onManageWindowsClickListener: () -> Unit, openInBrowserClickListener: (Uri) -> Unit, onCloseMenuClickListener: () -> Unit, onOutsideTouchListener: () -> Unit @@ -160,13 +164,15 @@ class HandleMenu( captionHeight = captionHeight, shouldShowWindowingPill = shouldShowWindowingPill, shouldShowBrowserPill = shouldShowBrowserPill, - shouldShowNewWindowButton = shouldShowNewWindowButton + shouldShowNewWindowButton = shouldShowNewWindowButton, + shouldShowManageWindowsButton = shouldShowManageWindowsButton ).apply { bind(taskInfo, appIconBitmap, appName) this.onToDesktopClickListener = onToDesktopClickListener this.onToFullscreenClickListener = onToFullscreenClickListener this.onToSplitScreenClickListener = onToSplitScreenClickListener this.onNewWindowClickListener = onNewWindowClickListener + this.onManageWindowsClickListener = onManageWindowsClickListener this.onOpenInBrowserClickListener = { openInBrowserClickListener.invoke(openInBrowserLink!!) } @@ -372,7 +378,13 @@ class HandleMenu( R.dimen.desktop_mode_handle_menu_new_window_height ) } - if (!SHOULD_SHOW_SCREENSHOT_BUTTON && !shouldShowNewWindowButton) { + if (!shouldShowManageWindowsButton) { + menuHeight -= loadDimensionPixelSize( + R.dimen.desktop_mode_handle_menu_manage_windows_height + ) + } + if (!SHOULD_SHOW_SCREENSHOT_BUTTON && !shouldShowNewWindowButton + && !shouldShowManageWindowsButton) { menuHeight -= pillTopMargin } if (!shouldShowBrowserPill) { @@ -405,7 +417,8 @@ class HandleMenu( captionHeight: Int, private val shouldShowWindowingPill: Boolean, private val shouldShowBrowserPill: Boolean, - private val shouldShowNewWindowButton: Boolean + private val shouldShowNewWindowButton: Boolean, + private val shouldShowManageWindowsButton: Boolean ) { val rootView = LayoutInflater.from(context) .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View @@ -430,6 +443,8 @@ class HandleMenu( private val moreActionsPill = rootView.requireViewById<View>(R.id.more_actions_pill) private val screenshotBtn = moreActionsPill.requireViewById<Button>(R.id.screenshot_button) private val newWindowBtn = moreActionsPill.requireViewById<Button>(R.id.new_window_button) + private val manageWindowBtn = moreActionsPill + .requireViewById<Button>(R.id.manage_windows_button) // Open in Browser Pill. private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill) @@ -446,6 +461,7 @@ class HandleMenu( var onToFullscreenClickListener: (() -> Unit)? = null var onToSplitScreenClickListener: (() -> Unit)? = null var onNewWindowClickListener: (() -> Unit)? = null + var onManageWindowsClickListener: (() -> Unit)? = null var onOpenInBrowserClickListener: (() -> Unit)? = null var onCloseMenuClickListener: (() -> Unit)? = null var onOutsideTouchListener: (() -> Unit)? = null @@ -457,6 +473,7 @@ class HandleMenu( browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() } collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() } newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() } + manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() } rootView.setOnTouchListener { _, event -> if (event.actionMasked == ACTION_OUTSIDE) { @@ -587,6 +604,7 @@ class HandleMenu( private fun bindMoreActionsPill(style: MenuStyle) { moreActionsPill.apply { isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON + && !shouldShowManageWindowsButton } screenshotBtn.apply { isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON @@ -603,6 +621,13 @@ class HandleMenu( setTextColor(style.textColor) compoundDrawableTintList = ColorStateList.valueOf(style.textColor) } + manageWindowBtn.apply { + isGone = !shouldShowManageWindowsButton + background.colorFilter = + BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY) + setTextColor(style.textColor) + compoundDrawableTintList = ColorStateList.valueOf(style.textColor) + } } private fun bindOpenInBrowserPill(style: MenuStyle) { @@ -643,6 +668,7 @@ interface HandleMenuFactory { splitScreenController: SplitScreenController, shouldShowWindowingPill: Boolean, shouldShowNewWindowButton: Boolean, + shouldShowManageWindowsButton: Boolean, openInBrowserLink: Uri?, captionWidth: Int, captionHeight: Int, @@ -661,6 +687,7 @@ object DefaultHandleMenuFactory : HandleMenuFactory { splitScreenController: SplitScreenController, shouldShowWindowingPill: Boolean, shouldShowNewWindowButton: Boolean, + shouldShowManageWindowsButton: Boolean, openInBrowserLink: Uri?, captionWidth: Int, captionHeight: Int, @@ -675,6 +702,7 @@ object DefaultHandleMenuFactory : HandleMenuFactory { splitScreenController, shouldShowWindowingPill, shouldShowNewWindowButton, + shouldShowManageWindowsButton, openInBrowserLink, captionWidth, captionHeight, 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/SplitDragPolicyTest.java index 645b296930f8..46b60499a01d 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/SplitDragPolicyTest.java @@ -30,11 +30,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; -import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_FULLSCREEN; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT; +import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -72,7 +72,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target; +import com.android.wm.shell.draganddrop.SplitDragPolicy.Target; import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.After; @@ -92,7 +92,7 @@ import java.util.HashSet; */ @SmallTest @RunWith(AndroidJUnit4.class) -public class DragAndDropPolicyTest extends ShellTestCase { +public class SplitDragPolicyTest extends ShellTestCase { @Mock private Context mContext; @@ -104,7 +104,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { @Mock private SplitScreenController mSplitScreenStarter; @Mock - private DragAndDropPolicy.Starter mFullscreenStarter; + private SplitDragPolicy.Starter mFullscreenStarter; @Mock private InstanceId mLoggerSessionId; @@ -112,7 +112,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { private DisplayLayout mLandscapeDisplayLayout; private DisplayLayout mPortraitDisplayLayout; private Insets mInsets; - private DragAndDropPolicy mPolicy; + private SplitDragPolicy mPolicy; private ClipData mActivityClipData; private PendingIntent mLaunchableIntentPendingIntent; @@ -150,7 +150,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false); mInsets = Insets.of(0, 0, 0, 0); - mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mFullscreenStarter)); + mPolicy = spy(new SplitDragPolicy(mContext, mSplitScreenStarter, mFullscreenStarter)); mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY); mLaunchableIntentPendingIntent = mock(PendingIntent.class); when(mLaunchableIntentPendingIntent.getCreatorUserHandle()) @@ -289,7 +289,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */); + mPolicy.onDropped(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */); verify(mFullscreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_UNDEFINED), any(), any()); } @@ -304,12 +304,12 @@ 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), null /* hideTaskToken */); + mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any()); reset(mSplitScreenStarter); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */); + mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any()); } @@ -324,12 +324,12 @@ 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), null /* hideTaskToken */); + mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any()); reset(mSplitScreenStarter); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), + mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 7b68ddf64fce..1741fe447fad 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -228,6 +228,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext = new TestableContext(mContext); mTestableContext.ensureTestableResources(); mContext.setMockPackageManager(mMockPackageManager); + when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())) + .thenReturn(false); when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel"); final ActivityInfo activityInfo = new ActivityInfo(); activityInfo.applicationInfo = new ApplicationInfo(); @@ -235,8 +237,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final Display defaultDisplay = mock(Display.class); doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY); doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); - when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), - any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt())) + when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(), + anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt())) .thenReturn(mMockHandleMenu); when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay))) @@ -744,6 +746,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { any(), any(), any(), + any(), openInBrowserCaptor.capture(), any(), any() @@ -771,6 +774,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { any(), any(), any(), + any(), openInBrowserCaptor.capture(), any(), any() @@ -821,6 +825,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { any(), any(), any(), + any(), closeClickListener.capture(), any() ); @@ -832,8 +837,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } private void verifyHandleMenuCreated(@Nullable Uri uri) { + verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(), - any(), anyBoolean(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt()); + any(), anyBoolean(), anyBoolean(), anyBoolean(), eq(uri), anyInt(), + anyInt(), anyInt()); } private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) { @@ -932,7 +939,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } private void createHandleMenu(@NonNull DesktopModeWindowDecoration decor) { - decor.createHandleMenu(); + decor.createHandleMenu(false); // Call DesktopModeWindowDecoration#onAssistContentReceived because decor waits to receive // {@link AssistContent} before creating the menu decor.onAssistContentReceived(mAssistContent); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt index 100abbbb8332..a84523112d9b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt @@ -236,11 +236,11 @@ class HandleMenuTest : ShellTestCase() { val handleMenu = HandleMenu(mockDesktopWindowDecoration, WindowManagerWrapper(mockWindowManager), layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true, - shouldShowNewWindowButton = true, + shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false, null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50, captionX = captionX ) - handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock()) + handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock()) return handleMenu } diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java index 11406fa74e4b..3cc111f6e099 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java @@ -140,7 +140,7 @@ public class ZenMode implements Parcelable { private static Status computeStatus(@NonNull ZenModeConfig.ZenRule zenRuleExtraData) { if (zenRuleExtraData.enabled) { - if (zenRuleExtraData.isAutomaticActive()) { + if (zenRuleExtraData.isActive()) { return Status.ENABLED_AND_ACTIVE; } else { return Status.ENABLED; diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index be4e9a14b043..f59eab001be9 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -934,9 +934,9 @@ android_robolectric_test { "androidx.compose.runtime_runtime", ], libs: [ - "android.test.runner", - "android.test.base", - "android.test.mock", + "android.test.runner.stubs.system", + "android.test.base.stubs.system", + "android.test.mock.stubs.system", "truth", ], diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index f660808fb015..f64d0ed31287 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -6,6 +6,7 @@ import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.transitions import com.android.systemui.bouncer.ui.composable.Bouncer import com.android.systemui.notifications.ui.composable.Notifications +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade @@ -20,7 +21,11 @@ import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTran import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition +import com.android.systemui.scene.ui.composable.transitions.notificationsShadeToQuickSettingsShadeTransition import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition +import com.android.systemui.scene.ui.composable.transitions.toNotificationsShadeTransition +import com.android.systemui.scene.ui.composable.transitions.toQuickSettingsShadeTransition +import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.composable.Shade /** @@ -74,6 +79,14 @@ val SceneContainerTransitions = transitions { from(Scenes.Lockscreen, to = Scenes.Gone) { lockscreenToGoneTransition() } from(Scenes.Shade, to = Scenes.QuickSettings) { shadeToQuickSettingsTransition() } + // Overlay transitions + + to(Overlays.NotificationsShade) { toNotificationsShadeTransition() } + to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() } + from(Overlays.NotificationsShade, Overlays.QuickSettingsShade) { + notificationsShadeToQuickSettingsShadeTransition() + } + // Scene overscroll overscrollDisabled(Scenes.Gone, Orientation.Vertical) @@ -91,4 +104,10 @@ val SceneContainerTransitions = transitions { y = Shade.Dimensions.ScrimOverscrollLimit, ) } + overscroll(Overlays.NotificationsShade, Orientation.Vertical) { + translate(OverlayShade.Elements.Panel, y = OverlayShade.Dimensions.OverscrollLimit) + } + overscroll(Overlays.QuickSettingsShade, Orientation.Vertical) { + translate(OverlayShade.Elements.Panel, y = OverlayShade.Dimensions.OverscrollLimit) + } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt deleted file mode 100644 index 48ec198a790a..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt +++ /dev/null @@ -1,25 +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.scene.ui.composable.transitions - -import com.android.compose.animation.scene.TransitionBuilder - -fun TransitionBuilder.goneToNotificationsShadeTransition( - durationScale: Double = 1.0, -) { - toNotificationsShadeTransition(durationScale) -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt deleted file mode 100644 index 8a03e29e6377..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt +++ /dev/null @@ -1,27 +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.scene.ui.composable.transitions - -import com.android.compose.animation.scene.Edge -import com.android.compose.animation.scene.TransitionBuilder - -fun TransitionBuilder.goneToQuickSettingsShadeTransition( - edge: Edge = Edge.Top, - durationScale: Double = 1.0, -) { - toQuickSettingsShadeTransition(edge, durationScale) -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt deleted file mode 100644 index 19aa3a7197d2..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt +++ /dev/null @@ -1,26 +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.scene.ui.composable.transitions - -import com.android.compose.animation.scene.Edge -import com.android.compose.animation.scene.TransitionBuilder - -fun TransitionBuilder.lockscreenToQuickSettingsShadeTransition( - durationScale: Double = 1.0, -) { - toQuickSettingsShadeTransition(Edge.Top, durationScale) -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt index 02664c1cc91e..24f285e81da2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt @@ -16,10 +16,22 @@ package com.android.systemui.scene.ui.composable.transitions +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween import com.android.compose.animation.scene.TransitionBuilder +import com.android.systemui.shade.ui.composable.Shade +import kotlin.time.Duration.Companion.milliseconds -fun TransitionBuilder.lockscreenToNotificationsShadeTransition( - durationScale: Double = 1.0, +fun TransitionBuilder.notificationsShadeToQuickSettingsShadeTransition( + durationScale: Double = 1.0 ) { - toNotificationsShadeTransition(durationScale = durationScale) + spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) + swipeSpec = + spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold, + ) } + +private val DefaultDuration = 300.milliseconds diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt index 9d13647bc43f..55fa6ad94ed3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt @@ -26,10 +26,7 @@ import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.composable.Shade import kotlin.time.Duration.Companion.milliseconds -fun TransitionBuilder.toQuickSettingsShadeTransition( - edge: Edge = Edge.Top, - durationScale: Double = 1.0, -) { +fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) swipeSpec = spring( @@ -38,7 +35,7 @@ fun TransitionBuilder.toQuickSettingsShadeTransition( ) distance = UserActionDistance { fromSceneSize, _ -> fromSceneSize.height.toFloat() * 2 / 3f } - translate(OverlayShade.Elements.Panel, edge) + translate(OverlayShade.Elements.Panel, Edge.Top) fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt index 1981a2d612e4..fb7387dfa9eb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.shared.model.DismissAction @@ -79,12 +79,11 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { dismissInteractor = dismissInteractor, applicationScope = testScope.backgroundScope, sceneInteractor = { kosmos.sceneInteractor }, - deviceEntryInteractor = { kosmos.deviceEntryInteractor }, + deviceUnlockedInteractor = { kosmos.deviceUnlockedInteractor }, quickSettingsSceneFamilyResolver = { kosmos.quickSettingsSceneFamilyResolver }, notifShadeSceneFamilyResolver = { kosmos.notifShadeSceneFamilyResolver }, powerInteractor = kosmos.powerInteractor, alternateBouncerInteractor = kosmos.alternateBouncerInteractor, - keyguardInteractor = { kosmos.keyguardInteractor }, shadeInteractor = { kosmos.shadeInteractor }, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt index f6865f137071..642d9a0b1e9d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -28,6 +29,7 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayActionsViewModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -47,7 +49,7 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() { private val underTest = kosmos.notificationsShadeOverlayActionsViewModel @Test - fun upTransitionSceneKey_hidesShade() = + fun up_hidesShade() = testScope.runTest { val actions by collectLastValue(underTest.actions) underTest.activateIn(this) @@ -66,4 +68,22 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() { assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay) .isEqualTo(Overlays.NotificationsShade) } + + @Test + fun downFromTopRight_switchesToQuickSettingsShade() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + underTest.activateIn(this) + + assertThat( + (actions?.get( + Swipe( + direction = SwipeDirection.Down, + fromSource = SceneContainerEdge.TopRight, + ) + ) as? UserActionResult.ReplaceByOverlay) + ?.overlay + ) + .isEqualTo(Overlays.QuickSettingsShade) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt index 762941d70389..fd1c043f1a29 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -28,6 +29,7 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -46,7 +48,7 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() { private val underTest = kosmos.quickSettingsShadeOverlayActionsViewModel @Test - fun upTransitionSceneKey_hidesShade() = + fun up_hidesShade() = testScope.runTest { val actions by collectLastValue(underTest.actions) underTest.activateIn(this) @@ -57,12 +59,44 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() { } @Test - fun back_hidesShade() = + fun back_notEditing_hidesShade() = testScope.runTest { val actions by collectLastValue(underTest.actions) + val isEditing by + collectLastValue(kosmos.quickSettingsContainerViewModel.editModeViewModel.isEditing) underTest.activateIn(this) + assertThat(isEditing).isFalse() assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay) .isEqualTo(Overlays.QuickSettingsShade) } + + @Test + fun back_whileEditing_doesNotHideShade() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + underTest.activateIn(this) + + kosmos.quickSettingsContainerViewModel.editModeViewModel.startEditing() + + assertThat(actions?.get(Back)).isNull() + } + + @Test + fun downFromTopLeft_switchesToNotificationsShade() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + underTest.activateIn(this) + + assertThat( + (actions?.get( + Swipe( + direction = SwipeDirection.Down, + fromSource = SceneContainerEdge.TopLeft, + ) + ) as? UserActionResult.ReplaceByOverlay) + ?.overlay + ) + .isEqualTo(Overlays.NotificationsShade) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt index 6986cf8ee7dc..62b6391ca54c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt @@ -37,7 +37,6 @@ import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn -import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneBackInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -46,7 +45,6 @@ import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before @@ -61,7 +59,7 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() }) + private val qsFlexiglassAdapter = kosmos.fakeQsSceneAdapter private val sceneInteractor = kosmos.sceneInteractor private val sceneBackInteractor = kosmos.sceneBackInteractor @@ -101,10 +99,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { mapOf( Back to UserActionResult(Scenes.Shade), Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), - Swipe( - fromSource = Edge.Bottom, - direction = SwipeDirection.Up, - ) to UserActionResult(SceneFamilies.Home) + Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to + UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Gone) @@ -130,10 +126,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { mapOf( Back to UserActionResult(Scenes.Lockscreen), Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Lockscreen), - Swipe( - fromSource = Edge.Bottom, - direction = SwipeDirection.Up, - ) to UserActionResult(SceneFamilies.Home) + Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to + UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Lockscreen) @@ -161,10 +155,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { mapOf( Back to UserActionResult(Scenes.Shade), Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), - Swipe( - fromSource = Edge.Bottom, - direction = SwipeDirection.Up, - ) to UserActionResult(SceneFamilies.Home) + Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to + UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Gone) @@ -187,10 +179,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { mapOf( Back to UserActionResult(Scenes.Shade), Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), - Swipe( - fromSource = Edge.Bottom, - direction = SwipeDirection.Up, - ) to UserActionResult(SceneFamilies.Home) + Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to + UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Lockscreen) @@ -225,10 +215,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { mapOf( Back to UserActionResult(Scenes.Shade), Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), - Swipe( - fromSource = Edge.Bottom, - direction = SwipeDirection.Up, - ) to UserActionResult(SceneFamilies.Home) + Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to + UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Gone) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index c28bce2e0de1..6d6cd45eaa58 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -294,7 +294,9 @@ constructor( /** Tell the bouncer that bouncer is requested when device is already authenticated */ fun notifyUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int) { - applicationScope.launch { repository.setKeyguardAuthenticatedPrimaryAuth(userId) } + applicationScope.launch { + repository.setUserRequestedBouncerWhenAlreadyAuthenticated(userId) + } } /** Tell the bouncer that keyguard is authenticated with biometrics. */ diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt index 73a8810f48d7..c60f93244437 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt @@ -17,12 +17,14 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.compose.runtime.getValue -import com.android.keyguard.ViewMediatorCallback import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import com.android.systemui.util.kotlin.Utils.Companion.sample +import com.android.systemui.util.kotlin.sample import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.coroutineScope @@ -34,7 +36,7 @@ constructor( private val legacyInteractor: PrimaryBouncerInteractor, private val authenticationInteractor: AuthenticationInteractor, private val selectedUserInteractor: SelectedUserInteractor, - private val viewMediatorCallback: ViewMediatorCallback?, + private val deviceUnlockedInteractor: DeviceUnlockedInteractor, ) : ExclusiveActivatable() { private val hydrator = Hydrator("BouncerContainerViewModel") @@ -45,6 +47,18 @@ constructor( override suspend fun onActivated(): Nothing { coroutineScope { launch { + legacyInteractor.isShowing + .sample(deviceUnlockedInteractor.deviceUnlockStatus, ::Pair) + .collect { (isShowing, unlockStatus) -> + if (isShowing && unlockStatus.isUnlocked) { + legacyInteractor.notifyUserRequestedBouncerWhenAlreadyAuthenticated( + selectedUserInteractor.getSelectedUserId() + ) + } + } + } + + launch { authenticationInteractor.onAuthenticationResult.collect { authenticationSucceeded -> if (authenticationSucceeded) { legacyInteractor.notifyKeyguardAuthenticatedPrimaryAuth( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt index 8495778a426f..873acbb55c30 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt @@ -21,7 +21,7 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.DismissAction import com.android.systemui.keyguard.shared.model.KeyguardDone @@ -36,6 +36,7 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter import com.android.systemui.util.kotlin.sample +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -61,14 +62,13 @@ constructor( transitionInteractor: KeyguardTransitionInteractor, val dismissInteractor: KeyguardDismissInteractor, @Application private val applicationScope: CoroutineScope, - sceneInteractor: dagger.Lazy<SceneInteractor>, - deviceEntryInteractor: dagger.Lazy<DeviceEntryInteractor>, - quickSettingsSceneFamilyResolver: dagger.Lazy<QuickSettingsSceneFamilyResolver>, - notifShadeSceneFamilyResolver: dagger.Lazy<NotifShadeSceneFamilyResolver>, + sceneInteractor: Lazy<SceneInteractor>, + deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>, + quickSettingsSceneFamilyResolver: Lazy<QuickSettingsSceneFamilyResolver>, + notifShadeSceneFamilyResolver: Lazy<NotifShadeSceneFamilyResolver>, powerInteractor: PowerInteractor, alternateBouncerInteractor: AlternateBouncerInteractor, - keyguardInteractor: dagger.Lazy<KeyguardInteractor>, - shadeInteractor: dagger.Lazy<ShadeInteractor>, + shadeInteractor: Lazy<ShadeInteractor>, ) { val dismissAction: Flow<DismissAction> = repository.dismissAction @@ -106,18 +106,19 @@ constructor( if (SceneContainerFlag.isEnabled) { combine( sceneInteractor.get().currentScene, - deviceEntryInteractor.get().isUnlocked, - ) { scene, isUnlocked -> - isUnlocked && + deviceUnlockedInteractor.get().deviceUnlockStatus, + ) { scene, unlockStatus -> + unlockStatus.isUnlocked && (quickSettingsSceneFamilyResolver.get().includesScene(scene) || notifShadeSceneFamilyResolver.get().includesScene(scene)) } .distinctUntilChanged() } else if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) { - shadeInteractor.get().isAnyExpanded.sample( - keyguardInteractor.get().isKeyguardDismissible - ) { isAnyExpanded, isKeyguardDismissible -> - isAnyExpanded && isKeyguardDismissible + combine( + shadeInteractor.get().isAnyExpanded, + deviceUnlockedInteractor.get().deviceUnlockStatus, + ) { isAnyExpanded, deviceUnlockStatus -> + isAnyExpanded && deviceUnlockStatus.isUnlocked } } else { flow { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt index 44aafabb103a..ad1a32e70a5b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt @@ -76,7 +76,7 @@ constructor( .filter { enabled -> !enabled } .sampleCombine( internalTransitionInteractor.currentTransitionInfoInternal, - biometricSettingsRepository.isCurrentUserInLockdown + biometricSettingsRepository.isCurrentUserInLockdown, ) .map { (_, transitionInfo, inLockdown) -> // ...we hide the keyguard, if it's showing and we're not in lockdown. In that case, @@ -91,12 +91,18 @@ constructor( */ scope.launch { if (!SceneContainerFlag.isEnabled) { - showKeyguardWhenReenabled - .filter { shouldDismiss -> shouldDismiss } - .collect { - keyguardDismissTransitionInteractor.startDismissKeyguardTransition( - "keyguard disabled" - ) + repository.isKeyguardEnabled + .filter { enabled -> !enabled } + .sampleCombine( + biometricSettingsRepository.isCurrentUserInLockdown, + internalTransitionInteractor.currentTransitionInfoInternal, + ) + .collect { (_, inLockdown, currentTransitionInfo) -> + if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) { + keyguardDismissTransitionInteractor.startDismissKeyguardTransition( + "keyguard disabled" + ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt index b6868c172a9f..63bfbd1dc1ba 100644 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt @@ -18,9 +18,13 @@ package com.android.systemui.notifications.ui.viewmodel import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult +import com.android.compose.animation.scene.UserActionResult.HideOverlay +import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -32,8 +36,10 @@ class NotificationsShadeOverlayActionsViewModel @AssistedInject constructor() : override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) { setActions( mapOf( - Swipe.Up to UserActionResult.HideOverlay(Overlays.NotificationsShade), - Back to UserActionResult.HideOverlay(Overlays.NotificationsShade), + Swipe.Up to HideOverlay(Overlays.NotificationsShade), + Back to HideOverlay(Overlays.NotificationsShade), + Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to + ReplaceByOverlay(Overlays.QuickSettingsShade), ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt index 61c4c8c0de86..31519a95d621 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt @@ -18,24 +18,41 @@ package com.android.systemui.qs.ui.viewmodel import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult +import com.android.compose.animation.scene.UserActionResult.HideOverlay +import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.map /** Models the UI state for the user actions for navigating to other scenes or overlays. */ -class QuickSettingsShadeOverlayActionsViewModel @AssistedInject constructor() : +class QuickSettingsShadeOverlayActionsViewModel +@AssistedInject +constructor(private val containerViewModel: QuickSettingsContainerViewModel) : UserActionsViewModel() { override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) { - setActions( - buildMap { - put(Swipe.Up, UserActionResult.HideOverlay(Overlays.QuickSettingsShade)) - put(Back, UserActionResult.HideOverlay(Overlays.QuickSettingsShade)) + containerViewModel.editModeViewModel.isEditing + .map { isEditing -> + buildMap { + put(Swipe.Up, HideOverlay(Overlays.QuickSettingsShade)) + // When editing, back should go back to QS from edit mode (i.e. remain in the + // same overlay). + if (!isEditing) { + put(Back, HideOverlay(Overlays.QuickSettingsShade)) + } + put( + Swipe(SwipeDirection.Down, fromSource = SceneContainerEdge.TopLeft), + ReplaceByOverlay(Overlays.NotificationsShade), + ) + } } - ) + .collect { actions -> setActions(actions) } } @AssistedFactory diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt index f5bcc21056e8..feee0a3ed08e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt @@ -38,6 +38,7 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.Mockito.inOrder import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations @@ -79,6 +80,26 @@ class RotationChangeProviderTest : SysuiTestCase() { } @Test + fun onRotationChanged_rotationSentMultipleWithTheSameValue_listenerReceivesUpdateOnce() { + sendRotationUpdate(42) + sendRotationUpdate(42) + sendRotationUpdate(42) + + verify(listener).onRotationChanged(42) + } + + @Test + fun onRotationChanged_rotationSentMultipleTimesWithDifferentValues_listenerReceivesUpdates() { + sendRotationUpdate(0) + sendRotationUpdate(1) + + with(inOrder(listener)) { + verify(listener).onRotationChanged(0) + verify(listener).onRotationChanged(1) + } + } + + @Test fun onRotationChanged_subscribersRemoved_noRotationChangeReceived() { sendRotationUpdate(42) verify(listener).onRotationChanged(42) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt index d920c4f05b03..62a721de64af 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt @@ -17,7 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor -import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope @@ -37,12 +37,11 @@ val Kosmos.keyguardDismissActionInteractor by dismissInteractor = keyguardDismissInteractor, applicationScope = testScope.backgroundScope, sceneInteractor = { sceneInteractor }, - deviceEntryInteractor = { deviceEntryInteractor }, + deviceUnlockedInteractor = { deviceUnlockedInteractor }, quickSettingsSceneFamilyResolver = { quickSettingsSceneFamilyResolver }, notifShadeSceneFamilyResolver = { notifShadeSceneFamilyResolver }, powerInteractor = powerInteractor, alternateBouncerInteractor = alternateBouncerInteractor, - keyguardInteractor = { keyguardInteractor }, shadeInteractor = { shadeInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt index 8fc40e492bdc..6ced8c3a5cd8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt @@ -18,8 +18,12 @@ package com.android.systemui.qs.ui.viewmodel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter +import com.android.systemui.util.mockito.mock + +val Kosmos.fakeQsSceneAdapter: FakeQSSceneAdapter by Fixture { FakeQSSceneAdapter({ mock() }) } val Kosmos.quickSettingsShadeOverlayActionsViewModel: QuickSettingsShadeOverlayActionsViewModel by Fixture { - QuickSettingsShadeOverlayActionsViewModel() + QuickSettingsShadeOverlayActionsViewModel(quickSettingsContainerViewModel) } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt index 0458f535c1eb..b1e6fe246f2c 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt @@ -103,7 +103,7 @@ constructor( if (displayId == display.displayId) { val currentRotation = display.rotation - if (lastRotation.compareAndSet(lastRotation.get(), currentRotation)) { + if (lastRotation.getAndSet(currentRotation) != currentRotation) { listeners.forEach { it.onRotationChanged(currentRotation) } } } diff --git a/services/core/Android.bp b/services/core/Android.bp index 1b5b7e875db8..4e36e3ff9188 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -237,6 +237,7 @@ java_library_static { "dreams_flags_lib", "aconfig_new_storage_flags_lib", "powerstats_flags_lib", + "locksettings_flags_lib", ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java index 4da5cfc18089..9398c7a854d4 100644 --- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java +++ b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java @@ -34,7 +34,6 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.PowerManager; import android.os.SystemClock; import android.util.Log; import android.util.Slog; @@ -73,7 +72,6 @@ public class AdaptiveAuthService extends SystemService { private final LockSettingsInternal mLockSettings; private final BiometricManager mBiometricManager; private final KeyguardManager mKeyguardManager; - private final PowerManager mPowerManager; private final WindowManagerInternal mWindowManager; private final UserManagerInternal mUserManager; @VisibleForTesting @@ -93,7 +91,6 @@ public class AdaptiveAuthService extends SystemService { mBiometricManager = Objects.requireNonNull( context.getSystemService(BiometricManager.class)); mKeyguardManager = Objects.requireNonNull(context.getSystemService(KeyguardManager.class)); - mPowerManager = Objects.requireNonNull(context.getSystemService(PowerManager.class)); mWindowManager = Objects.requireNonNull( LocalServices.getService(WindowManagerInternal.class)); mUserManager = Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class)); @@ -290,9 +287,6 @@ public class AdaptiveAuthService extends SystemService { parentUserId); } - // Power off the display - mPowerManager.goToSleep(SystemClock.uptimeMillis()); - // Lock the device mWindowManager.lockNow(); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 2416ab68c1af..75e9fadbd917 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1833,7 +1833,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub @Override @EnforcePermission(UPDATE_DEVICE_STATS) - public void noteScreenState(final int state) { + public void noteScreenState(final int displayId, final int state, final int reason) { super.noteScreenState_enforcePermission(); synchronized (mLock) { @@ -1843,7 +1843,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandler.post(() -> { if (DBG) Slog.d(TAG, "begin noteScreenState"); synchronized (mStats) { - mStats.noteScreenStateLocked(0, state, elapsedRealtime, uptime, currentTime); + mStats.noteScreenStateLocked( + displayId, state, reason, elapsedRealtime, uptime, currentTime); } if (DBG) Slog.d(TAG, "end noteScreenState"); }); @@ -1853,7 +1854,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub @Override @EnforcePermission(UPDATE_DEVICE_STATS) - public void noteScreenBrightness(final int brightness) { + public void noteScreenBrightness(final int displayId, final int brightness) { super.noteScreenBrightness_enforcePermission(); synchronized (mLock) { @@ -1861,7 +1862,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long uptime = SystemClock.uptimeMillis(); mHandler.post(() -> { synchronized (mStats) { - mStats.noteScreenBrightnessLocked(0, brightness, elapsedRealtime, uptime); + mStats.noteScreenBrightnessLocked( + displayId, brightness, elapsedRealtime, uptime); } }); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index c3faec007552..04573f4b7514 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -536,7 +536,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mLastBrightnessEvent = new BrightnessEvent(mDisplayId); mTempBrightnessEvent = new BrightnessEvent(mDisplayId); - if (mDisplayId == Display.DEFAULT_DISPLAY) { + if (flags.isBatteryStatsEnabledForAllDisplays()) { + mBatteryStats = BatteryStatsService.getService(); + } else if (mDisplayId == Display.DEFAULT_DISPLAY) { mBatteryStats = BatteryStatsService.getService(); } else { mBatteryStats = null; @@ -2791,8 +2793,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call screenState, mDisplayStatsId, reason); if (mBatteryStats != null) { try { - // TODO(multi-display): make this multi-display - mBatteryStats.noteScreenState(screenState); + mBatteryStats.noteScreenState(mDisplayId, screenState, reason); } catch (RemoteException e) { // same process } @@ -2807,7 +2808,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call int brightnessInt = mFlags.isBrightnessIntRangeUserPerceptionEnabled() ? BrightnessSynchronizer.brightnessFloatToIntSetting(mContext, brightness) : BrightnessSynchronizer.brightnessFloatToInt(brightness); - mBatteryStats.noteScreenBrightness(brightnessInt); + mBatteryStats.noteScreenBrightness(mDisplayId, brightnessInt); } catch (RemoteException e) { // same process } diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 69b67c87afb9..f600e7fc2946 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -200,6 +200,11 @@ public class DisplayManagerFlags { Flags::normalBrightnessForDozeParameter ); + private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState( + Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS, + Flags::enableBatteryStatsForAllDisplays + ); + /** * @return {@code true} if 'port' is allowed in display layout configuration file. */ @@ -415,6 +420,14 @@ public class DisplayManagerFlags { } /** + * @return {@code true} if battery stats is enabled for all displays, not just the primary + * display. + */ + public boolean isBatteryStatsEnabledForAllDisplays() { + return mEnableBatteryStatsForAllDisplays.isEnabled(); + } + + /** * dumps all flagstates * @param pw printWriter */ @@ -456,6 +469,7 @@ public class DisplayManagerFlags { pw.println(" " + mNewHdrBrightnessModifier); pw.println(" " + mNormalBrightnessForDozeParameter); pw.println(" " + mIdleScreenConfigInSubscribingLightSensor); + pw.println(" " + mEnableBatteryStatsForAllDisplays); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 70230b48a672..9968ba57bba4 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -349,3 +349,11 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_battery_stats_for_all_displays" + namespace: "display_manager" + description: "Flag to enable battery stats for all displays." + bug: "366112793" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/locksettings/Android.bp b/services/core/java/com/android/server/locksettings/Android.bp new file mode 100644 index 000000000000..53f1ac668e49 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/Android.bp @@ -0,0 +1,11 @@ +aconfig_declarations { + name: "locksettings_flags", + package: "com.android.server.locksettings", + container: "system", + srcs: ["*.aconfig"], +} + +java_aconfig_library { + name: "locksettings_flags_lib", + aconfig_declarations: "locksettings_flags", +} diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index f44b85273af6..820c0efcc1cf 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -273,11 +273,6 @@ class RebootEscrowManager { "server_based_ror_enabled", false); } - public boolean waitForInternet() { - return DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_OTA, "wait_for_internet_ror", false); - } - public boolean isNetworkConnected() { final ConnectivityManager connectivityManager = mContext.getSystemService(ConnectivityManager.class); @@ -433,7 +428,7 @@ class RebootEscrowManager { /** Wrapper function to set error code serialized through handler, */ private void setLoadEscrowDataErrorCode(@RebootEscrowErrorCode int value, Handler handler) { - if (mInjector.waitForInternet()) { + if (Flags.waitForInternetRor()) { mInjector.post( handler, () -> { @@ -516,7 +511,7 @@ class RebootEscrowManager { mWakeLock.acquire(mInjector.getWakeLockTimeoutMillis()); } - if (mInjector.waitForInternet()) { + if (Flags.waitForInternetRor()) { // Timeout to stop retrying same as the wake lock timeout. mInjector.postDelayed( retryHandler, @@ -553,7 +548,7 @@ class RebootEscrowManager { return; } - if (mInjector.waitForInternet()) { + if (Flags.waitForInternetRor()) { if (mRebootEscrowTimedOut) { Slog.w(TAG, "Failed to load reboot escrow data within timeout"); compareAndSetLoadEscrowDataErrorCode( diff --git a/services/core/java/com/android/server/locksettings/flags.aconfig b/services/core/java/com/android/server/locksettings/flags.aconfig new file mode 100644 index 000000000000..6818de91c98e --- /dev/null +++ b/services/core/java/com/android/server/locksettings/flags.aconfig @@ -0,0 +1,9 @@ +package: "com.android.server.locksettings" +container: "system" + +flag { + name: "wait_for_internet_ror" + namespace: "sudo" + description: "Feature flag to wait for internet connectivity before calling resume on reboot server." + bug: "231660348" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java index b03a54ec0cd3..fcc5e9771f94 100644 --- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java +++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java @@ -419,7 +419,7 @@ class ZenModeEventLogger { if (config.automaticRules != null) { for (ZenModeConfig.ZenRule rule : config.automaticRules.values()) { - if (rule != null && rule.isAutomaticActive()) { + if (rule != null && rule.isActive()) { rules.add(rule); } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 2ada9ae4790d..e9db1b529a63 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -887,7 +887,7 @@ public class ZenModeHelper { return Condition.STATE_UNKNOWN; } if (Flags.modesApi() && Flags.modesUi()) { - return rule.isAutomaticActive() ? STATE_TRUE : STATE_FALSE; + return rule.isActive() ? STATE_TRUE : STATE_FALSE; } else { // Buggy, does not consider snoozing! return rule.condition != null ? rule.condition.state : STATE_FALSE; @@ -967,12 +967,12 @@ public class ZenModeHelper { // snoozing-unsnoozing or activating-stopping. if (condition.state == STATE_TRUE) { rule.resetConditionOverride(); - if (!rule.isAutomaticActive()) { + if (!rule.isActive()) { rule.setConditionOverride(OVERRIDE_ACTIVATE); } } else if (condition.state == STATE_FALSE) { rule.resetConditionOverride(); - if (rule.isAutomaticActive()) { + if (rule.isActive()) { rule.setConditionOverride(OVERRIDE_DEACTIVATE); } } @@ -1609,7 +1609,7 @@ public class ZenModeHelper { // User deactivation of DND means just turning off the manual DND rule. // For API calls (different origin) keep old behavior of snoozing all rules. for (ZenRule automaticRule : newConfig.automaticRules.values()) { - if (automaticRule.isAutomaticActive()) { + if (automaticRule.isActive()) { automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE); } } @@ -1618,7 +1618,7 @@ public class ZenModeHelper { if (zenMode == Global.ZEN_MODE_OFF) { newConfig.manualRule = null; for (ZenRule automaticRule : newConfig.automaticRules.values()) { - if (automaticRule.isAutomaticActive()) { + if (automaticRule.isActive()) { automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE); } } @@ -1665,7 +1665,7 @@ public class ZenModeHelper { mConfig.manualRule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS); } for (ZenRule rule : mConfig.automaticRules.values()) { - if (rule.isAutomaticActive()) { + if (rule.isActive()) { rule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS); } } @@ -2020,9 +2020,9 @@ public class ZenModeHelper { scheduleEnabledBroadcast( rule.getPkg(), config.user, rule.id, rule.enabled); } - if (original.isAutomaticActive() != rule.isAutomaticActive()) { + if (original.isActive() != rule.isActive()) { scheduleActivationBroadcast( - rule.getPkg(), config.user, rule.id, rule.isAutomaticActive()); + rule.getPkg(), config.user, rule.id, rule.isActive()); } } } @@ -2106,7 +2106,7 @@ public class ZenModeHelper { if (mConfig.isManualActive()) return mConfig.manualRule.zenMode; int zen = Global.ZEN_MODE_OFF; for (ZenRule automaticRule : mConfig.automaticRules.values()) { - if (automaticRule.isAutomaticActive()) { + if (automaticRule.isActive()) { if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) { // automatic rule triggered dnd and user hasn't seen update dnd dialog if (Settings.Secure.getInt(mContext.getContentResolver(), @@ -2182,7 +2182,7 @@ public class ZenModeHelper { } for (ZenRule automaticRule : mConfig.automaticRules.values()) { - if (automaticRule.isAutomaticActive()) { + if (automaticRule.isActive()) { // Active rules with INTERRUPTION_FILTER_ALL are not included in consolidated // policy. This is relevant in case some other active rule has a more // restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy! diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 680b1acedf78..cb8e1a0f35b8 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -5031,9 +5031,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mPretendScreenOff != pretendScreenOff) { mPretendScreenOff = pretendScreenOff; final int primaryScreenState = mPerDisplayBatteryStats[0].screenState; - noteScreenStateLocked(0, primaryScreenState, - mClock.elapsedRealtime(), mClock.uptimeMillis(), - mClock.currentTimeMillis()); + noteScreenStateLocked(0, primaryScreenState); } } @@ -5554,15 +5552,29 @@ public class BatteryStatsImpl extends BatteryStats { } } + private static String getScreenStateTag( + int display, int state, @Display.StateReason int reason) { + return String.format( + "display=%d state=%s reason=%s", + display, Display.stateToString(state), Display.stateReasonToString(reason)); + } + @GuardedBy("this") public void noteScreenStateLocked(int display, int state) { - noteScreenStateLocked(display, state, mClock.elapsedRealtime(), mClock.uptimeMillis(), - mClock.currentTimeMillis()); + noteScreenStateLocked(display, state, Display.STATE_REASON_UNKNOWN, + mClock.elapsedRealtime(), mClock.uptimeMillis(), mClock.currentTimeMillis()); } @GuardedBy("this") public void noteScreenStateLocked(int display, int displayState, - long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) { + @Display.StateReason int displayStateReason, long elapsedRealtimeMs, long uptimeMs, + long currentTimeMs) { + if (Flags.batteryStatsScreenStateEvent()) { + mHistory.recordEvent( + elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_DISPLAY_STATE_CHANGED, + getScreenStateTag(display, displayState, displayStateReason), + Process.INVALID_UID); + } // Battery stats relies on there being 4 states. To accommodate this, new states beyond the // original 4 are mapped to one of the originals. if (displayState > MAX_TRACKED_SCREEN_STATE) { diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index cc0a283db6a0..05d29f50085c 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -68,3 +68,11 @@ flag { description: "Disable deprecated BatteryUsageStatsAtom pulled atom" bug: "324602949" } + +flag { + name: "battery_stats_screen_state_event" + namespace: "backstage_power" + description: "Guards the battery stats event for screen state changes." + bug: "364350206" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b5276303f6b9..1640ad3f1958 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2754,10 +2754,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * @param outRegion The region to update. */ private void updateRegionForModalActivityWindow(Region outRegion) { - // If the inner bounds of letterbox is available, then it will be used as the - // touchable region so it won't cover the touchable letterbox and the touch - // events can slip to activity from letterbox. - mActivityRecord.getLetterboxInnerBounds(mTmpRect); + if (Flags.scrollingFromLetterbox()) { + // Touchable region expands to the letterbox area to react to scrolls from letterbox. + mTmpRect.setEmpty(); + } else { + // If the activity is letterboxed and scrolling from letterbox is disabled, limit the + // touchable region to the activity. This way, the letterbox area is exposed to react + // to touch events, and the touch events can slip from the activity from letterbox. + mActivityRecord.getLetterboxInnerBounds(mTmpRect); + } + if (mTmpRect.isEmpty()) { final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds(); if (transformedBounds != null) { diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp index b5cf98697d54..9560ec9990ad 100644 --- a/services/tests/appfunctions/Android.bp +++ b/services/tests/appfunctions/Android.bp @@ -45,8 +45,8 @@ android_test { ], libs: [ - "android.test.base", - "android.test.runner", + "android.test.base.stubs.system", + "android.test.runner.stubs.system", ], certificate: "platform", diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index d0aec3b6cef8..bf5a692ef8ca 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -75,6 +75,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.app.IBatteryStats; import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; @@ -158,6 +159,8 @@ public final class DisplayPowerControllerTest { private DisplayManagerFlags mDisplayManagerFlagsMock; @Mock private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + @Mock + private IBatteryStats mMockBatteryStats; @Captor private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; @@ -204,7 +207,8 @@ public final class DisplayPowerControllerTest { doAnswer((Answer<Void>) invocationOnMock -> null).when(() -> SystemProperties.set(anyString(), any())); - doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService); + doAnswer((Answer<IBatteryStats>) invocationOnMock -> mMockBatteryStats) + .when(BatteryStatsService::getService); doAnswer((Answer<Boolean>) invocationOnMock -> false) .when(ActivityManager::isLowRamDeviceStatic); @@ -2227,6 +2231,52 @@ public final class DisplayPowerControllerTest { verify(mHolder.brightnessSetting).saveIfNeeded(); } + @Test + public void testBatteryStatNotes_enabledOnDefaultDisplayWhenDisabledOnOthers() + throws Exception { + when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(false); + + verifyNoteScreenState(Display.DEFAULT_DISPLAY, /* expectNote= */ true); + } + + @Test + public void testBatteryStatNotes_enabledOnDefaultDisplayWhenEnabledOnOthers() throws Exception { + when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(true); + + verifyNoteScreenState(Display.DEFAULT_DISPLAY, /* expectNote= */ true); + } + + @Test + public void testBatteryStatNotes_flagGuardedOnNonDefaultDisplays() throws Exception { + when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(false); + + verifyNoteScreenState(/* displayId= */ 2, /* expectNote= */ false); + + when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(true); + + verifyNoteScreenState(/* displayId= */ 2, /* expectNote= */ true); + } + + private void verifyNoteScreenState(int displayId, boolean expectNote) throws Exception { + mHolder = createDisplayPowerController(displayId, UNIQUE_ID); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + if (expectNote) { + verify(mMockBatteryStats) + .noteScreenState( + displayId, Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY); + verify(mMockBatteryStats).noteScreenBrightness(eq(displayId), anyInt()); + } else { + verify(mMockBatteryStats, never()).noteScreenState(anyInt(), anyInt(), anyInt()); + verify(mMockBatteryStats, never()).noteScreenBrightness(anyInt(), anyInt()); + } + } + /** * Creates a mock and registers it to {@link LocalServices}. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp index 127d3e8a4136..7ac7aca3fd59 100644 --- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp +++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp @@ -39,9 +39,9 @@ android_test { ], libs: [ - "android.test.mock", - "android.test.base", - "android.test.runner", + "android.test.mock.stubs.system", + "android.test.base.stubs.system", + "android.test.runner.stubs.system", ], jni_libs: [ diff --git a/services/tests/ondeviceintelligencetests/Android.bp b/services/tests/ondeviceintelligencetests/Android.bp index aa859422f54f..a31a3fb65700 100644 --- a/services/tests/ondeviceintelligencetests/Android.bp +++ b/services/tests/ondeviceintelligencetests/Android.bp @@ -47,9 +47,9 @@ android_test { ], libs: [ - "android.test.mock", - "android.test.base", - "android.test.runner", + "android.test.mock.stubs.system", + "android.test.base.stubs.system", + "android.test.runner.stubs.system", ], certificate: "platform", diff --git a/services/tests/performancehinttests/Android.bp b/services/tests/performancehinttests/Android.bp index 1692921cdb2d..c8121fc6930a 100644 --- a/services/tests/performancehinttests/Android.bp +++ b/services/tests/performancehinttests/Android.bp @@ -19,7 +19,7 @@ android_test { "truth", ], libs: [ - "android.test.base", + "android.test.base.stubs.system", ], test_suites: [ "automotive-tests", diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java index f74cfae6a81b..c0be8652f303 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java @@ -56,14 +56,14 @@ public class AmbientDisplayPowerCalculatorTest { stats.updateDisplayEnergyConsumerStatsLocked(new long[]{300_000_000}, new int[]{Display.STATE_ON}, 0); - stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, - 30 * MINUTE_IN_MS); + stats.noteScreenStateLocked(0, Display.STATE_DOZE, Display.STATE_REASON_DEFAULT_POLICY, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); stats.updateDisplayEnergyConsumerStatsLocked(new long[]{200_000_000}, new int[]{Display.STATE_DOZE}, 30 * MINUTE_IN_MS); - stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, - 120 * MINUTE_IN_MS); + stats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_DEFAULT_POLICY, + 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS); stats.updateDisplayEnergyConsumerStatsLocked(new long[]{100_000_000}, new int[]{Display.STATE_OFF}, 120 * MINUTE_IN_MS); @@ -93,37 +93,37 @@ public class AmbientDisplayPowerCalculatorTest { final int[] screenStates = new int[] {Display.STATE_OFF, Display.STATE_OFF}; - stats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0); - stats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0); + stats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN, 0, 0, 0); + stats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN, 0, 0, 0); stats.updateDisplayEnergyConsumerStatsLocked(new long[]{300, 400}, screenStates, 0); // Switch display0 to doze screenStates[0] = Display.STATE_DOZE; - stats.noteScreenStateLocked(0, screenStates[0], 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, - 30 * MINUTE_IN_MS); + stats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); stats.updateDisplayEnergyConsumerStatsLocked(new long[]{200, 300}, screenStates, 30 * MINUTE_IN_MS); // Switch display1 to doze screenStates[1] = Display.STATE_DOZE; - stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS, - 90 * MINUTE_IN_MS); + stats.noteScreenStateLocked(1, Display.STATE_DOZE, Display.STATE_REASON_UNKNOWN, + 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS); // 100,000,000 uC should be attributed to display 0 doze here. stats.updateDisplayEnergyConsumerStatsLocked(new long[]{100_000_000, 700_000_000}, screenStates, 90 * MINUTE_IN_MS); // Switch display0 to off screenStates[0] = Display.STATE_OFF; - stats.noteScreenStateLocked(0, screenStates[0], 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, - 120 * MINUTE_IN_MS); + stats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN, + 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS); // 40,000,000 and 70,000,000 uC should be attributed to display 0 and 1 doze here. stats.updateDisplayEnergyConsumerStatsLocked(new long[]{40_000_000, 70_000_000}, screenStates, 120 * MINUTE_IN_MS); // Switch display1 to off screenStates[1] = Display.STATE_OFF; - stats.noteScreenStateLocked(1, screenStates[1], 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS, - 150 * MINUTE_IN_MS); + stats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN, + 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS); stats.updateDisplayEnergyConsumerStatsLocked(new long[]{100, 90_000_000}, screenStates, 150 * MINUTE_IN_MS); // 90,000,000 uC should be attributed to display 1 doze here. @@ -148,10 +148,10 @@ public class AmbientDisplayPowerCalculatorTest { public void testPowerProfileBasedModel() { BatteryStatsImpl stats = mStatsRule.getBatteryStats(); - stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, - 30 * MINUTE_IN_MS); - stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, - 120 * MINUTE_IN_MS); + stats.noteScreenStateLocked(0, Display.STATE_DOZE, Display.STATE_REASON_UNKNOWN, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + stats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, + 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS); AmbientDisplayPowerCalculator calculator = new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile()); @@ -174,15 +174,15 @@ public class AmbientDisplayPowerCalculatorTest { BatteryStatsImpl stats = mStatsRule.getBatteryStats(); - stats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0); - stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, - 30 * MINUTE_IN_MS); - stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS, - 90 * MINUTE_IN_MS); - stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, - 120 * MINUTE_IN_MS); - stats.noteScreenStateLocked(1, Display.STATE_OFF, 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS, - 150 * MINUTE_IN_MS); + stats.noteScreenStateLocked(1, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, 0, 0, 0); + stats.noteScreenStateLocked(0, Display.STATE_DOZE, Display.STATE_REASON_UNKNOWN, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + stats.noteScreenStateLocked(1, Display.STATE_DOZE, Display.STATE_REASON_UNKNOWN, + 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS); + stats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, + 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS); + stats.noteScreenStateLocked(1, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, + 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS); AmbientDisplayPowerCalculator calculator = new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile()); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java index afbe9159b66a..2ccb6420bc43 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java @@ -44,6 +44,10 @@ import android.os.Looper; import android.os.Process; import android.os.UserHandle; import android.os.WorkSource; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.DisabledOnRavenwood; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.ravenwood.RavenwoodRule; import android.telephony.AccessNetworkConstants; import android.telephony.ActivityStatsTechSpecificInfo; @@ -65,6 +69,7 @@ import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import com.android.internal.power.EnergyConsumerStats; +import com.android.server.power.optimization.Flags; import com.android.server.power.stats.BatteryStatsImpl.DualTimer; import org.junit.Rule; @@ -90,6 +95,8 @@ public class BatteryStatsNoteTest { .setProvideMainThread(true) .build(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String TAG = BatteryStatsNoteTest.class.getSimpleName(); private static final int UID = 10500; @@ -104,6 +111,54 @@ public class BatteryStatsNoteTest { @Mock NetworkStatsManager mNetworkStatsManager; + @DisabledOnRavenwood + @EnableFlags(Flags.FLAG_BATTERY_STATS_SCREEN_STATE_EVENT) + @Test + public void testScreenStateEvent_screenStateEventFlagOn_eventsRecorded() throws Exception { + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClock()); + bi.forceRecordAllHistory(); + + bi.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY, + 0, 0, 0); + bi.noteScreenStateLocked(2, Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DRAW_WAKE_LOCK, + 1, 1, 1); + + BatteryStatsHistoryIterator iterator = + bi.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED); + BatteryStats.HistoryItem item = + iterateAndFind(iterator, HistoryItem.EVENT_DISPLAY_STATE_CHANGED); + assertThat(item).isNotNull(); + assertThat(item.eventTag).isNotNull(); + assertThat(item.eventTag.string).isEqualTo("display=0 state=ON reason=DEFAULT_POLICY"); + assertThat(item.eventTag.uid).isEqualTo(Process.INVALID_UID); + + item = iterateAndFind(iterator, HistoryItem.EVENT_DISPLAY_STATE_CHANGED); + assertThat(item).isNotNull(); + assertThat(item.eventTag).isNotNull(); + assertThat(item.eventTag.string) + .isEqualTo("display=2 state=DOZE_SUSPEND reason=DRAW_WAKE_LOCK"); + assertThat(item.eventTag.uid).isEqualTo(Process.INVALID_UID); + + // Last check to make sure that we did not record any extra event. + assertThat(iterateAndFind(iterator, HistoryItem.EVENT_DISPLAY_STATE_CHANGED)).isNull(); + } + + @DisableFlags(Flags.FLAG_BATTERY_STATS_SCREEN_STATE_EVENT) + @Test + public void testScreenStateEvent_screenStateEventFlagOff_eventsNotRecorded() throws Exception { + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClock()); + bi.forceRecordAllHistory(); + + bi.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY, + 0, 0, 0); + bi.noteScreenStateLocked(2, Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DRAW_WAKE_LOCK, + 1, 1, 1); + + BatteryStatsHistoryIterator iterator = + bi.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED); + assertThat(iterateAndFind(iterator, HistoryItem.EVENT_DISPLAY_STATE_CHANGED)).isNull(); + } + /** * Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked. */ @@ -285,20 +340,15 @@ public class BatteryStatsNoteTest { final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED); - BatteryStats.HistoryItem item; + BatteryStats.HistoryItem item = + iterateAndFind(iterator, HistoryItem.EVENT_LONG_WAKE_LOCK_START); - while ((item = iterator.next()) != null) { - if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break; - } - assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START); + assertThat(item).isNotNull(); assertThat(item.eventTag).isNotNull(); assertThat(item.eventTag.string).isEqualTo(historyName); assertThat(item.eventTag.uid).isEqualTo(UID); - while ((item = iterator.next()) != null) { - if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break; - } - assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH); + item = iterateAndFind(iterator, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH); assertThat(item.eventTag).isNotNull(); assertThat(item.eventTag.string).isEqualTo(historyName); assertThat(item.eventTag.uid).isEqualTo(UID); @@ -343,20 +393,15 @@ public class BatteryStatsNoteTest { final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED); - BatteryStats.HistoryItem item; - - while ((item = iterator.next()) != null) { - if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break; - } - assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START); + BatteryStats.HistoryItem item = + iterateAndFind(iterator, HistoryItem.EVENT_LONG_WAKE_LOCK_START); + assertThat(item).isNotNull(); assertThat(item.eventTag).isNotNull(); assertThat(item.eventTag.string).isEqualTo(historyName); assertThat(item.eventTag.uid).isEqualTo(UID); - while ((item = iterator.next()) != null) { - if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break; - } - assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH); + item = iterateAndFind(iterator, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH); + assertThat(item).isNotNull(); assertThat(item.eventTag).isNotNull(); assertThat(item.eventTag.string).isEqualTo(historyName); assertThat(item.eventTag.uid).isEqualTo(UID); @@ -2562,4 +2607,18 @@ public class BatteryStatsNoteTest { currentTimeMs, currentTimeMs, mNetworkStatsManager); } } + + /** + * Moves a given {@link BatteryStatsHistoryIterator} until a history item with the given + * {@code eventCode} is found and returns the history item. Returns {@code null} if no such item + * is found. + */ + private static BatteryStats.HistoryItem iterateAndFind( + BatteryStatsHistoryIterator iterator, int eventCode) { + BatteryStats.HistoryItem item; + while ((item = iterator.next()) != null) { + if (item.eventCode == eventCode) return item; + } + return null; + } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java index 88d4ea75501d..2da98e8b9a61 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java @@ -61,7 +61,8 @@ public class ScreenPowerCalculatorTest { mStatsRule.initMeasuredEnergyStatsLocked(); BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0); + batteryStats.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_UNKNOWN, + 0, 0, 0); batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{0}, new int[]{Display.STATE_ON}, 0); setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, @@ -79,7 +80,7 @@ public class ScreenPowerCalculatorTest { batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{300_000_000}, new int[]{Display.STATE_ON}, 60 * MINUTE_IN_MS); - batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, + batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); @@ -150,8 +151,10 @@ public class ScreenPowerCalculatorTest { final int[] screenStates = new int[]{Display.STATE_ON, Display.STATE_OFF}; - batteryStats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0); - batteryStats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0); + batteryStats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN, + 0, 0, 0); + batteryStats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN, + 0, 0, 0); batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0); setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0); batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{300, 400}, screenStates, 0); @@ -166,10 +169,10 @@ public class ScreenPowerCalculatorTest { screenStates[0] = Display.STATE_OFF; screenStates[1] = Display.STATE_ON; - batteryStats.noteScreenStateLocked(0, screenStates[0], + batteryStats.noteScreenStateLocked(0, screenStates[0], Display.STATE_REASON_UNKNOWN, + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); - batteryStats.noteScreenStateLocked(1, screenStates[1], 80 * MINUTE_IN_MS, - 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{600_000_000, 500}, screenStates, 80 * MINUTE_IN_MS); @@ -178,8 +181,8 @@ public class ScreenPowerCalculatorTest { batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS); screenStates[1] = Display.STATE_OFF; - batteryStats.noteScreenStateLocked(1, screenStates[1], 110 * MINUTE_IN_MS, - 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); + batteryStats.noteScreenStateLocked(1, screenStates[1], Display.STATE_REASON_UNKNOWN, + 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{700, 800_000_000}, screenStates, 110 * MINUTE_IN_MS); @@ -240,7 +243,8 @@ public class ScreenPowerCalculatorTest { public void testPowerProfileBasedModel() { BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0); + batteryStats.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_UNKNOWN, + 0, 0, 0); batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0); setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0); @@ -253,7 +257,7 @@ public class ScreenPowerCalculatorTest { setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); - batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, + batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); @@ -313,8 +317,10 @@ public class ScreenPowerCalculatorTest { BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0); - batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0); + batteryStats.noteScreenStateLocked(0, Display.STATE_ON, Display.STATE_REASON_UNKNOWN, + 0, 0, 0); + batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, + 0, 0, 0); batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0); setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0); @@ -327,16 +333,16 @@ public class ScreenPowerCalculatorTest { setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); - batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, + batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.noteScreenStateLocked(1, Display.STATE_ON, Display.STATE_REASON_UNKNOWN, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); - batteryStats.noteScreenStateLocked(1, Display.STATE_ON, 80 * MINUTE_IN_MS, - 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); batteryStats.noteScreenBrightnessLocked(1, 20, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); batteryStats.noteScreenBrightnessLocked(1, 250, 86 * MINUTE_IN_MS, 86 * MINUTE_IN_MS); batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS); - batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 110 * MINUTE_IN_MS, - 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); + batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, Display.STATE_REASON_UNKNOWN, + 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false, 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp index 12a70387affd..048978ab88a3 100644 --- a/services/tests/selinux/Android.bp +++ b/services/tests/selinux/Android.bp @@ -42,9 +42,9 @@ android_test { "mockito_extended", ], libs: [ - "android.test.base", - "android.test.mock", - "android.test.runner", + "android.test.base.stubs.system", + "android.test.mock.stubs.system", + "android.test.runner.stubs.system", "servicestests-core-utils", ], static_libs: [ diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index b41b30cf0e2e..bbe0755b9cc9 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -91,6 +91,7 @@ android_test { "net_flags_lib", "CtsVirtualDeviceCommonLib", "com_android_server_accessibility_flags_lib", + "locksettings_flags_lib", ], libs: [ diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index d6f7e21a2069..d071c159d6f5 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -60,6 +60,9 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -70,6 +73,7 @@ import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnReb import com.android.server.pm.UserManagerInternal; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -108,6 +112,9 @@ public class RebootEscrowManagerTests { 0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23, }; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private Context mContext; private UserManager mUserManager; private UserManagerInternal mUserManagerInternal; @@ -145,7 +152,6 @@ public class RebootEscrowManagerTests { private RebootEscrowProviderInterface mRebootEscrowProviderInUse; private ConnectivityManager.NetworkCallback mNetworkCallback; private Consumer<ConnectivityManager.NetworkCallback> mNetworkConsumer; - private boolean mWaitForInternet; MockInjector( Context context, @@ -159,7 +165,6 @@ public class RebootEscrowManagerTests { super(context, storage, userManagerInternal); mRebootEscrow = rebootEscrow; mServerBased = false; - mWaitForInternet = false; RebootEscrowProviderHalImpl.Injector halInjector = new RebootEscrowProviderHalImpl.Injector() { @Override @@ -185,7 +190,6 @@ public class RebootEscrowManagerTests { super(context, storage, userManagerInternal); mRebootEscrow = null; mServerBased = true; - mWaitForInternet = false; RebootEscrowProviderServerBasedImpl.Injector injector = new RebootEscrowProviderServerBasedImpl.Injector(serviceConnection) { @Override @@ -227,15 +231,6 @@ public class RebootEscrowManagerTests { } @Override - public boolean waitForInternet() { - return mWaitForInternet; - } - - public void setWaitForNetwork(boolean waitForNetworkEnabled) { - mWaitForInternet = waitForNetworkEnabled; - } - - @Override public boolean isNetworkConnected() { return false; } @@ -934,10 +929,10 @@ public class RebootEscrowManagerTests { } @Test + @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternet_success() throws Exception { setServerBasedRebootEscrowProvider(); - mMockInjector.setWaitForNetwork(true); when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); @@ -987,10 +982,10 @@ public class RebootEscrowManagerTests { } @Test + @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternetRemoteException_Failure() throws Exception { setServerBasedRebootEscrowProvider(); - mMockInjector.setWaitForNetwork(true); when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); @@ -1042,10 +1037,10 @@ public class RebootEscrowManagerTests { } @Test + @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_waitForInternet_networkUnavailable() throws Exception { setServerBasedRebootEscrowProvider(); - mMockInjector.setWaitForNetwork(true); when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); @@ -1090,9 +1085,9 @@ public class RebootEscrowManagerTests { } @Test + @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_waitForInternet_networkLost() throws Exception { setServerBasedRebootEscrowProvider(); - mMockInjector.setWaitForNetwork(true); when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); @@ -1145,10 +1140,10 @@ public class RebootEscrowManagerTests { } @Test + @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_waitForInternet_networkAvailableWithDelay() throws Exception { setServerBasedRebootEscrowProvider(); - mMockInjector.setWaitForNetwork(true); when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); @@ -1204,10 +1199,10 @@ public class RebootEscrowManagerTests { } @Test + @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_waitForInternet_timeoutExhausted() throws Exception { setServerBasedRebootEscrowProvider(); - mMockInjector.setWaitForNetwork(true); when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); @@ -1264,10 +1259,10 @@ public class RebootEscrowManagerTests { } @Test + @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_serverBasedWaitForNetwork_retryCountExhausted() throws Exception { setServerBasedRebootEscrowProvider(); - mMockInjector.setWaitForNetwork(true); when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); @@ -1320,10 +1315,10 @@ public class RebootEscrowManagerTests { } @Test + @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_ServerBasedWaitForInternet_RetrySuccess() throws Exception { setServerBasedRebootEscrowProvider(); - mMockInjector.setWaitForNetwork(true); when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index f8ff1f45e89c..efcf027a0b90 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -756,7 +756,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals("a", fromXml.getPkg()); fromXml.condition = new Condition(Uri.EMPTY, "", Condition.STATE_TRUE); - assertTrue(fromXml.isAutomaticActive()); + assertTrue(fromXml.isActive()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index baa633f16f67..39a9d30e7a92 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -5788,7 +5788,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // ... but it is NOT active ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(newRuleId); - assertThat(storedRule.isAutomaticActive()).isFalse(); + assertThat(storedRule.isActive()).isFalse(); assertThat(storedRule.isTrueOrUnknown()).isFalse(); assertThat(storedRule.condition).isNull(); assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); @@ -5841,7 +5841,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // ... but it is NEITHER active NOR snoozed. ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(newRuleId); - assertThat(storedRule.isAutomaticActive()).isFalse(); + assertThat(storedRule.isActive()).isFalse(); assertThat(storedRule.isTrueOrUnknown()).isFalse(); assertThat(storedRule.condition).isNull(); assertThat(storedRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); @@ -6619,7 +6619,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.isActive()).isTrue(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); assertThat(zenRule.condition).isNull(); @@ -6627,14 +6627,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.isActive()).isFalse(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); assertThat(zenRule.condition).isNull(); // Bonus check: app has resumed control over the rule and can now turn it on. mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.isActive()).isTrue(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); assertThat(zenRule.condition).isEqualTo(autoOn); } @@ -6655,7 +6655,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.isActive()).isTrue(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); assertThat(zenRule.condition).isEqualTo(autoOn); @@ -6663,7 +6663,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.isActive()).isFalse(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); assertThat(zenRule.condition).isEqualTo(autoOn); @@ -6671,14 +6671,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.isActive()).isTrue(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); assertThat(zenRule.condition).isEqualTo(autoOn); // Bonus check: app has resumed control over the rule and can now turn it off. mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOff, ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.isActive()).isFalse(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); assertThat(zenRule.condition).isEqualTo(autoOff); } @@ -6696,7 +6696,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), ORIGIN_APP, CUSTOM_PKG_UID); ZenRule zenRuleOn = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRuleOn.isAutomaticActive()).isTrue(); + assertThat(zenRuleOn.isActive()).isTrue(); assertThat(zenRuleOn.getConditionOverride()).isEqualTo(OVERRIDE_NONE); assertThat(zenRuleOn.condition).isNotNull(); @@ -6704,7 +6704,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); ZenRule zenRuleOff = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRuleOff.isAutomaticActive()).isFalse(); + assertThat(zenRuleOff.isActive()).isFalse(); assertThat(zenRuleOff.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); assertThat(zenRuleOff.condition).isNotNull(); } @@ -6723,27 +6723,27 @@ public class ZenModeHelperTest extends UiServiceTestCase { new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.isActive()).isTrue(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT), ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.isActive()).isTrue(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.isActive()).isTrue(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT), ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.isActive()).isFalse(); } @Test @@ -6760,35 +6760,35 @@ public class ZenModeHelperTest extends UiServiceTestCase { new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.isActive()).isTrue(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.isActive()).isFalse(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.isActive()).isFalse(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT), ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.isActive()).isFalse(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), ORIGIN_APP, CUSTOM_PKG_UID); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); - assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.isActive()).isTrue(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); } @@ -6805,14 +6805,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "manual-on-from-sysui", STATE_TRUE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); - assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue(); + assertThat(getZenRule(ruleId).isActive()).isTrue(); assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); // ... and they can turn it off manually from inside the app. mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "manual-off-from-app", STATE_FALSE, SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID); - assertThat(getZenRule(ruleId).isAutomaticActive()).isFalse(); + assertThat(getZenRule(ruleId).isActive()).isFalse(); assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE); } @@ -6829,21 +6829,21 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "auto-on-from-app", STATE_TRUE, SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID); - assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue(); + assertThat(getZenRule(ruleId).isActive()).isTrue(); assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE); // User manually turns off rule from SysUI / Settings... mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "manual-off-from-sysui", STATE_FALSE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); - assertThat(getZenRule(ruleId).isAutomaticActive()).isFalse(); + assertThat(getZenRule(ruleId).isActive()).isFalse(); assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); // ... and they can turn it on manually from inside the app. mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE, SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID); - assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue(); + assertThat(getZenRule(ruleId).isActive()).isTrue(); assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE); } @@ -6861,14 +6861,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE, SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID); - assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue(); + assertThat(getZenRule(ruleId).isActive()).isTrue(); assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE); // ... so the app can turn it off when its schedule is over. mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "auto-off-from-app", STATE_FALSE, SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID); - assertThat(getZenRule(ruleId).isAutomaticActive()).isFalse(); + assertThat(getZenRule(ruleId).isActive()).isFalse(); assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 5a54af10888f..2d5e5dacc217 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -80,6 +80,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; @@ -88,9 +89,12 @@ import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.Region; import android.os.IBinder; import android.os.InputConfig; import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.util.ArraySet; @@ -116,6 +120,7 @@ import androidx.test.filters.SmallTest; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.testutils.StubTransaction; import com.android.server.wm.SensitiveContentPackages.PackageInfo; +import com.android.window.flags.Flags; import org.junit.After; import org.junit.Test; @@ -965,6 +970,88 @@ public class WindowStateTests extends WindowTestsBase { assertTrue(testFlag(handle.inputConfig, InputConfig.NO_INPUT_CHANNEL)); } + @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX) + @Test + public void testTouchRegionUsesLetterboxBoundsIfTransformedBoundsAndLetterboxScrolling() { + final WindowState win = createWindow(null, TYPE_APPLICATION, "win"); + + // Transformed bounds used for size of touchable region if letterbox inner bounds are empty. + final Rect transformedBounds = new Rect(0, 0, 300, 500); + doReturn(transformedBounds).when(win.mToken).getFixedRotationTransformDisplayBounds(); + + // Otherwise, touchable region should match letterbox inner bounds. + final Rect letterboxInnerBounds = new Rect(30, 0, 270, 500); + doAnswer(invocation -> { + Rect rect = invocation.getArgument(0); + rect.set(letterboxInnerBounds); + return null; + }).when(win.mActivityRecord).getLetterboxInnerBounds(any()); + + Region outRegion = new Region(); + win.getSurfaceTouchableRegion(outRegion, win.mAttrs); + + // Because scrollingFromLetterbox flag is disabled and letterboxInnerBounds is not empty, + // touchable region should match letterboxInnerBounds always. + assertEquals(letterboxInnerBounds, outRegion.getBounds()); + } + + @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX) + @Test + public void testTouchRegionUsesLetterboxBoundsIfNullTransformedBoundsAndLetterboxScrolling() { + final WindowState win = createWindow(null, TYPE_APPLICATION, "win"); + + // Fragment bounds used for size of touchable region if letterbox inner bounds are empty + // and Transform bounds are null. + doReturn(null).when(win.mToken).getFixedRotationTransformDisplayBounds(); + final Rect fragmentBounds = new Rect(0, 0, 300, 500); + final TaskFragment taskFragment = win.mActivityRecord.getTaskFragment(); + doAnswer(invocation -> { + Rect rect = invocation.getArgument(0); + rect.set(fragmentBounds); + return null; + }).when(taskFragment).getDimBounds(any()); + + // Otherwise, touchable region should match letterbox inner bounds. + final Rect letterboxInnerBounds = new Rect(30, 0, 270, 500); + doAnswer(invocation -> { + Rect rect = invocation.getArgument(0); + rect.set(letterboxInnerBounds); + return null; + }).when(win.mActivityRecord).getLetterboxInnerBounds(any()); + + Region outRegion = new Region(); + win.getSurfaceTouchableRegion(outRegion, win.mAttrs); + + // Because scrollingFromLetterbox flag is disabled and letterboxInnerBounds is not empty, + // touchable region should match letterboxInnerBounds always. + assertEquals(letterboxInnerBounds, outRegion.getBounds()); + } + + @EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX) + @Test + public void testTouchRegionUsesTransformedBoundsIfLetterboxScrolling() { + final WindowState win = createWindow(null, TYPE_APPLICATION, "win"); + + // Transformed bounds used for size of touchable region if letterbox inner bounds are empty. + final Rect transformedBounds = new Rect(0, 0, 300, 500); + doReturn(transformedBounds).when(win.mToken).getFixedRotationTransformDisplayBounds(); + + // Otherwise, touchable region should match letterbox inner bounds. + final Rect letterboxInnerBounds = new Rect(30, 0, 270, 500); + doAnswer(invocation -> { + Rect rect = invocation.getArgument(0); + rect.set(letterboxInnerBounds); + return null; + }).when(win.mActivityRecord).getLetterboxInnerBounds(any()); + + Region outRegion = new Region(); + win.getSurfaceTouchableRegion(outRegion, win.mAttrs); + + // Because scrollingFromLetterbox flag is enabled and transformedBounds are non-null, + // touchable region should match transformedBounds. + assertEquals(transformedBounds, outRegion.getBounds()); + } + @Test public void testHasActiveVisibleWindow() { final int uid = ActivityBuilder.DEFAULT_FAKE_UID; diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt index d32cedb24a36..cd6ab30d8678 100644 --- a/tests/Input/src/com/android/test/input/AnrTest.kt +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -166,12 +166,12 @@ class AnrTest { val displayManager = instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val display = displayManager.getDisplay(obj.getDisplayId()) - val touchScreen = UinputTouchScreen(instrumentation, display) - val rect: Rect = obj.visibleBounds - val pointer = touchScreen.touchDown(rect.centerX(), rect.centerY()) - pointer.lift() - touchScreen.close() + UinputTouchScreen(instrumentation, display).use { touchScreen -> + touchScreen + .touchDown(rect.centerX(), rect.centerY()) + .lift() + } } private fun triggerAnr() { diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp index 2909e66b53be..331a21a0215b 100644 --- a/tests/UsbManagerTests/Android.bp +++ b/tests/UsbManagerTests/Android.bp @@ -44,7 +44,7 @@ android_test { "libstaticjvmtiagent", ], libs: [ - "android.test.mock", + "android.test.mock.stubs.system", ], certificate: "platform", platform_apis: true, |