diff options
161 files changed, 2869 insertions, 918 deletions
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig index a6e980726a9a..9181ef0c532a 100644 --- a/apex/jobscheduler/service/aconfig/alarm.aconfig +++ b/apex/jobscheduler/service/aconfig/alarm.aconfig @@ -7,3 +7,13 @@ flag { description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due" bug: "314907186" } + +flag { + name: "acquire_wakelock_before_send" + namespace: "backstage_power" + description: "Acquire the userspace alarm wakelock before sending the alarm" + bug: "391413964" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 829442aed6ac..f89b13dce307 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -5334,6 +5334,18 @@ public class AlarmManagerService extends SystemService { public void deliverLocked(Alarm alarm, long nowELAPSED) { final long workSourceToken = ThreadLocalWorkSource.setUid( getAlarmAttributionUid(alarm)); + + if (Flags.acquireWakelockBeforeSend()) { + // Acquire the wakelock before starting the app. This needs to be done to avoid + // random stalls in the receiving app in case a suspend attempt is already in + // progress. See b/391413964 for an incident where this was found to happen. + if (mBroadcastRefCount == 0) { + setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true); + mWakeLock.acquire(); + mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget(); + } + } + try { if (alarm.operation != null) { // PendingIntent alarm @@ -5399,14 +5411,16 @@ public class AlarmManagerService extends SystemService { ThreadLocalWorkSource.restore(workSourceToken); } - // The alarm is now in flight; now arrange wakelock and stats tracking if (DEBUG_WAKELOCK) { Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1)); } - if (mBroadcastRefCount == 0) { - setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true); - mWakeLock.acquire(); - mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget(); + if (!Flags.acquireWakelockBeforeSend()) { + // The alarm is now in flight; now arrange wakelock and stats tracking + if (mBroadcastRefCount == 0) { + setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true); + mWakeLock.acquire(); + mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget(); + } } final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED); mInFlight.add(inflight); diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 787fdee6ee16..3314b4aa0d71 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -648,7 +648,7 @@ java_api_library { java_api_library { name: "android-non-updatable.stubs.module_lib.from-text", - api_surface: "module_lib", + api_surface: "module-lib", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", @@ -668,7 +668,7 @@ java_api_library { // generated from this module, as this module is strictly used for hiddenapi only. java_api_library { name: "android-non-updatable.stubs.test_module_lib", - api_surface: "module_lib", + api_surface: "module-lib", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", @@ -689,7 +689,7 @@ java_api_library { java_api_library { name: "android-non-updatable.stubs.system_server.from-text", - api_surface: "system_server", + api_surface: "system-server", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f06bd48a8cd8..e2fe5062d356 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1634,6 +1634,10 @@ package android.hardware.camera2.params { method public void setColorSpace(@NonNull android.graphics.ColorSpace.Named); } + @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public final class SharedSessionConfiguration { + ctor public SharedSessionConfiguration(int, @NonNull long[]); + } + } package android.hardware.devicestate { diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 7454c44997d6..039f7b6a86ba 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -211,13 +211,13 @@ public class StatusBarManager { * * @hide */ - public static final int NAVIGATION_HINT_IME_SHOWN = 1 << 1; + public static final int NAVIGATION_HINT_IME_VISIBLE = 1 << 1; /** * The IME Switcher button is visible. This only takes effect while the IME is visible. * * @hide */ - public static final int NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN = 1 << 2; + public static final int NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE = 1 << 2; /** * Navigation bar flags related to the IME state. * @@ -225,8 +225,8 @@ public class StatusBarManager { */ @IntDef(flag = true, prefix = { "NAVIGATION_HINT_" }, value = { NAVIGATION_HINT_BACK_ALT, - NAVIGATION_HINT_IME_SHOWN, - NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN, + NAVIGATION_HINT_IME_VISIBLE, + NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE, }) @Retention(RetentionPolicy.SOURCE) public @interface NavigationHint {} @@ -1360,11 +1360,11 @@ public class StatusBarManager { if ((hints & NAVIGATION_HINT_BACK_ALT) != 0) { hintStrings.add("NAVIGATION_HINT_BACK_ALT"); } - if ((hints & NAVIGATION_HINT_IME_SHOWN) != 0) { - hintStrings.add("NAVIGATION_HINT_IME_SHOWN"); + if ((hints & NAVIGATION_HINT_IME_VISIBLE) != 0) { + hintStrings.add("NAVIGATION_HINT_IME_VISIBLE"); } - if ((hints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0) { - hintStrings.add("NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN"); + if ((hints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0) { + hintStrings.add("NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE"); } return String.join(" | ", hintStrings); } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 1035d8b93881..063055eb4917 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -1146,9 +1146,9 @@ public final class UiAutomation { executionStartTimeMillis = SystemClock.uptimeMillis(); eventQueueStartIndex = mEventQueue.size(); } - if (DEBUG) { - Log.d(LOG_TAG, "executeAndWaitForEvent: watchersCount=" + watchersDepth - + ", eventQueueStartIndex=" + eventQueueStartIndex); + if (VERBOSE) { + Log.v(LOG_TAG, "executeAndWaitForEvent starts at depth=" + watchersDepth + ", " + + "command=" + command + ", filter=" + filter + ", timeout=" + timeoutMillis); } try { @@ -1199,6 +1199,9 @@ public final class UiAutomation { } mLock.notifyAll(); } + if (VERBOSE) { + Log.v(LOG_TAG, "executeAndWaitForEvent ends at depth=" + watchersDepth); + } } } diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl index 4598421eb3bc..c3f3b1ced33c 100644 --- a/core/java/android/app/supervision/ISupervisionManager.aidl +++ b/core/java/android/app/supervision/ISupervisionManager.aidl @@ -22,4 +22,5 @@ package android.app.supervision; */ interface ISupervisionManager { boolean isSupervisionEnabledForUser(int userId); + String getActiveSupervisionAppPackage(int userId); } diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java index 92241f3634e8..12432ddd0eb9 100644 --- a/core/java/android/app/supervision/SupervisionManager.java +++ b/core/java/android/app/supervision/SupervisionManager.java @@ -16,6 +16,7 @@ package android.app.supervision; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; @@ -98,4 +99,20 @@ public class SupervisionManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns the package name of the app that is acting as the active supervision app or null if + * supervision is disabled. + * + * @hide + */ + @UserHandleAware + @Nullable + public String getActiveSupervisionAppPackage() { + try { + return mService.getActiveSupervisionAppPackage(mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index df1028e9e04c..b9b5c6a8bbc3 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityOptions; -import android.app.LoadedApk; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -753,9 +752,6 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW */ protected Context getRemoteContextEnsuringCorrectCachedApkPath() { try { - ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo; - LoadedApk.checkAndUpdateApkPaths(expectedAppInfo); - // Return if cloned successfully, otherwise default Context newContext = mContext.createApplicationContext( mInfo.providerInfo.applicationInfo, Context.CONTEXT_RESTRICTED); diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 6da2a073ec19..1cf42820f356 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -18,13 +18,6 @@ flag { } flag { - namespace: "virtual_devices" - name: "media_projection_keyguard_restrictions" - description: "Auto-stop MP when the device locks" - bug: "348335290" -} - -flag { namespace: "virtual_devices" name: "virtual_display_insets" description: "APIs for specifying virtual display insets (via cutout)" diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index 430ed2b68342..449423f1ea1f 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -133,6 +133,7 @@ flag { metadata { purpose: PURPOSE_BUGFIX } + is_exported: true } flag { diff --git a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java index de93234445ca..d3fb93588762 100644 --- a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java +++ b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java @@ -19,6 +19,7 @@ package android.hardware.biometrics; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.DevicePolicyManager; +import android.app.supervision.SupervisionManager; import android.content.ComponentName; import android.content.Context; import android.os.Build; @@ -55,27 +56,44 @@ public class ParentalControlsUtilsInternal { return null; } - public static boolean parentConsentRequired(@NonNull Context context, - @NonNull DevicePolicyManager dpm, @BiometricAuthenticator.Modality int modality, + /** @return true if parental consent is required in order for biometric sensors to be used. */ + public static boolean parentConsentRequired( + @NonNull Context context, + @NonNull DevicePolicyManager dpm, + @Nullable SupervisionManager sm, + @BiometricAuthenticator.Modality int modality, @NonNull UserHandle userHandle) { if (getTestComponentName(context, userHandle.getIdentifier()) != null) { return true; } - return parentConsentRequired(dpm, modality, userHandle); + return parentConsentRequired(dpm, sm, modality, userHandle); } /** * @return true if parental consent is required in order for biometric sensors to be used. */ - public static boolean parentConsentRequired(@NonNull DevicePolicyManager dpm, - @BiometricAuthenticator.Modality int modality, @NonNull UserHandle userHandle) { - final ComponentName cn = getSupervisionComponentName(dpm, userHandle); - if (cn == null) { - return false; + public static boolean parentConsentRequired( + @NonNull DevicePolicyManager dpm, + @Nullable SupervisionManager sm, + @BiometricAuthenticator.Modality int modality, + @NonNull UserHandle userHandle) { + final int keyguardDisabledFeatures; + + if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) { + if (sm != null && !sm.isSupervisionEnabledForUser(userHandle.getIdentifier())) { + return false; + } + // Check for keyguard features disabled by any admin. + keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(/* admin= */ null); + } else { + final ComponentName cn = getSupervisionComponentName(dpm, userHandle); + if (cn == null) { + return false; + } + keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn); } - final int keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn); final boolean dpmFpDisabled = containsFlag(keyguardDisabledFeatures, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); final boolean dpmFaceDisabled = containsFlag(keyguardDisabledFeatures, @@ -97,7 +115,9 @@ public class ParentalControlsUtilsInternal { return consentRequired; } + /** @deprecated Use {@link SupervisionManager} to check for supervision. */ @Nullable + @Deprecated public static ComponentName getSupervisionComponentName(@NonNull DevicePolicyManager dpm, @NonNull UserHandle userHandle) { return dpm.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle); diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 5533a640b9d8..210653bb41e5 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -5256,9 +5256,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</p> * </li> * </ul> - * <p>All of the above configurations can be set up with a SessionConfiguration. The list of - * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and - * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p> * <p>When set to BAKLAVA, the additional stream combinations below are verified * by the compliance tests:</p> * <table> @@ -5268,6 +5265,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <th style="text-align: center;">Size</th> * <th style="text-align: center;">Target 2</th> * <th style="text-align: center;">Size</th> + * <th style="text-align: center;">Target 3</th> + * <th style="text-align: center;">Size</th> * </tr> * </thead> * <tbody> @@ -5276,15 +5275,34 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <td style="text-align: center;">S1080P</td> * <td style="text-align: center;">PRIV</td> * <td style="text-align: center;">S1080P</td> + * <td style="text-align: center;"></td> + * <td style="text-align: center;"></td> * </tr> * <tr> * <td style="text-align: center;">PRIV</td> * <td style="text-align: center;">S1080P</td> * <td style="text-align: center;">PRIV</td> * <td style="text-align: center;">S1440P</td> + * <td style="text-align: center;"></td> + * <td style="text-align: center;"></td> + * </tr> + * <tr> + * <td style="text-align: center;">PRIV</td> + * <td style="text-align: center;">S1080P</td> + * <td style="text-align: center;">YUV</td> + * <td style="text-align: center;">S1080P</td> + * <td style="text-align: center;">S1080P</td> + * <td style="text-align: center;">PRIV</td> * </tr> * </tbody> * </table> + * <ul> + * <li>VIDEO_STABILIZATION_MODE: {OFF, ON} for the newly added stream combinations given the + * presence of dedicated video stream</li> + * </ul> + * <p>All of the above configurations can be set up with a SessionConfiguration. The list of + * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and + * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p> * <p>This key is available on all devices.</p> */ @PublicKey diff --git a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java index 365f870ba22d..b40c7d35c06b 100644 --- a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.graphics.ColorSpace; import android.graphics.ImageFormat.Format; import android.hardware.DataSpace.NamedDataSpace; @@ -228,6 +229,7 @@ public final class SharedSessionConfiguration { * * @hide */ + @TestApi public SharedSessionConfiguration(int sharedColorSpace, @NonNull long[] sharedOutputConfigurations) { mColorSpace = sharedColorSpace; diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index edc5cb7cdf6a..8ca139fdf9b9 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -17,8 +17,8 @@ package android.inputmethodservice; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; @@ -242,10 +242,10 @@ final class NavigationBarController { NavigationBarView.class::isInstance); if (navigationBarView != null) { // TODO(b/213337792): Support InputMethodService#setBackDisposition(). - // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary. - final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN + // TODO(b/213337792): Set NAVIGATION_HINT_IME_VISIBLE only when necessary. + final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_VISIBLE | (mShouldShowImeSwitcherWhenImeIsShown - ? NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN : 0); + ? NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE : 0); navigationBarView.setNavigationIconHints(hints); navigationBarView.prepareNavButtons(this); } @@ -515,10 +515,10 @@ final class NavigationBarController { NavigationBarView.class::isInstance); if (navigationBarView != null) { // TODO(b/213337792): Support InputMethodService#setBackDisposition(). - // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary. - final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN + // TODO(b/213337792): Set NAVIGATION_HINT_IME_VISIBLE only when necessary. + final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_VISIBLE | (mShouldShowImeSwitcherWhenImeIsShown - ? NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN : 0); + ? NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE : 0); navigationBarView.setNavigationIconHints(hints); } } else { diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java index 622d5d1b1c5a..5c0977115e36 100644 --- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java @@ -17,7 +17,7 @@ package android.inputmethodservice.navigationbar; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE; import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE; import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET; @@ -316,9 +316,10 @@ public final class NavigationBarView extends FrameLayout { getImeSwitchButton().setImageDrawable(mImeSwitcherIcon); - // Update IME button visibility, a11y and rotate button always overrides the appearance + // Update IME switcher button visibility, a11y and rotate button always overrides + // the appearance. final boolean isImeSwitcherButtonVisible = - (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0; + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0; getImeSwitchButton() .setVisibility(isImeSwitcherButtonVisible ? View.VISIBLE : View.INVISIBLE); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 7c75d7b30037..0e329c2859db 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -44,7 +44,6 @@ import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.Application; -import android.app.LoadedApk; import android.app.PendingIntent; import android.app.RemoteInput; import android.appwidget.AppWidgetHostView; @@ -8484,8 +8483,14 @@ public class RemoteViews implements Parcelable, Filter { return context; } try { - LoadedApk.checkAndUpdateApkPaths(mApplication); - Context applicationContext = context.createApplicationContext(mApplication, + // Use PackageManager as the source of truth for application information, rather + // than the parceled ApplicationInfo provided by the app. + ApplicationInfo sanitizedApplication = + context.getPackageManager().getApplicationInfoAsUser( + mApplication.packageName, 0, + UserHandle.getUserId(mApplication.uid)); + Context applicationContext = context.createApplicationContext( + sanitizedApplication, Context.CONTEXT_RESTRICTED); // Get the correct apk paths while maintaining the current context's configuration. return applicationContext.createConfigurationContext( diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index e51ef4f6d04c..82055afda8c1 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -94,7 +94,9 @@ public enum DesktopModeFlags { Flags::enableDesktopAppLaunchTransitionsBugfix, false), INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC( Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true), - ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true); + ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true), + ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true), + ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true); /** * Flag class, to be used in case the enum cannot be used because the flag is not accessible. diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index e35c3b80a58b..222088e8a8b9 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -583,3 +583,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_start_launch_transition_from_taskbar_bugfix" + namespace: "lse_desktop_experience" + description: "Enables starting a launch transition directly from the taskbar if desktop tasks are visible." + bug: "361366053" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/tests/FileSystemUtilsTest/Android.bp b/core/tests/FileSystemUtilsTest/Android.bp index 962ff3c0a6e0..f4d92522bb25 100644 --- a/core/tests/FileSystemUtilsTest/Android.bp +++ b/core/tests/FileSystemUtilsTest/Android.bp @@ -36,10 +36,10 @@ cc_library { ldflags: ["-z max-page-size=0x1000"], } -android_test_helper_app { - name: "app_with_4kb_elf", +java_defaults { + name: "app_with_4kb_elf_defaults", srcs: ["app_with_4kb_elf/src/**/*.java"], - manifest: "app_with_4kb_elf/app_with_4kb_elf.xml", + resource_dirs: ["app_with_4kb_elf/res"], compile_multilib: "64", jni_libs: [ "libpunchtest_4kb", @@ -47,7 +47,36 @@ android_test_helper_app { static_libs: [ "androidx.test.rules", "platform-test-annotations", + "androidx.test.uiautomator_uiautomator", + "sysui-helper", ], +} + +android_test_helper_app { + name: "app_with_4kb_elf", + defaults: ["app_with_4kb_elf_defaults"], + manifest: "app_with_4kb_elf/app_with_4kb_elf.xml", + use_embedded_native_libs: true, +} + +android_test_helper_app { + name: "app_with_4kb_compressed_elf", + defaults: ["app_with_4kb_elf_defaults"], + manifest: "app_with_4kb_elf/app_with_4kb_elf.xml", + use_embedded_native_libs: false, +} + +android_test_helper_app { + name: "page_size_compat_disabled_app", + defaults: ["app_with_4kb_elf_defaults"], + manifest: "app_with_4kb_elf/page_size_compat_disabled.xml", + use_embedded_native_libs: true, +} + +android_test_helper_app { + name: "app_with_4kb_elf_no_override", + defaults: ["app_with_4kb_elf_defaults"], + manifest: "app_with_4kb_elf/app_with_4kb_no_override.xml", use_embedded_native_libs: true, } @@ -99,6 +128,9 @@ java_test_host { ":embedded_native_libs_test_app", ":extract_native_libs_test_app", ":app_with_4kb_elf", + ":page_size_compat_disabled_app", + ":app_with_4kb_compressed_elf", + ":app_with_4kb_elf_no_override", ], test_suites: ["general-tests"], test_config: "AndroidTest.xml", diff --git a/core/tests/FileSystemUtilsTest/AndroidTest.xml b/core/tests/FileSystemUtilsTest/AndroidTest.xml index 651a7ca15dac..27f49b2289ba 100644 --- a/core/tests/FileSystemUtilsTest/AndroidTest.xml +++ b/core/tests/FileSystemUtilsTest/AndroidTest.xml @@ -22,7 +22,6 @@ <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="embedded_native_libs_test_app.apk" /> <option name="test-file-name" value="extract_native_libs_test_app.apk" /> - <option name="test-file-name" value="app_with_4kb_elf.apk" /> </target_preparer> <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml index b9d6d4db2c81..d7a37336cbc3 100644 --- a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml +++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml @@ -18,7 +18,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android.test.pagesizecompat"> <application - android:extractNativeLibs="false" android:pageSizeCompat="enabled"> <uses-library android:name="android.test.runner"/> <activity android:name=".MainActivity" diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml new file mode 100644 index 000000000000..b0b5204d6e80 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2025 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.pagesizecompat"> + <application + android:label="PageSizeCompatTestApp"> + <uses-library android:name="android.test.runner"/> + <activity android:name=".MainActivity" + android:exported="true" + android:label="Home page" + android:process=":NewProcess"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> + </application> + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.pagesizecompat"/> +</manifest>
\ No newline at end of file diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/page_size_compat_disabled.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/page_size_compat_disabled.xml new file mode 100644 index 000000000000..641c5e741014 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/page_size_compat_disabled.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2025 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.pagesizecompat"> + <application + android:pageSizeCompat="disabled"> + <uses-library android:name="android.test.runner"/> + <activity android:name=".MainActivity" + android:exported="true" + android:process=":NewProcess"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> + </application> + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.pagesizecompat"/> +</manifest>
\ No newline at end of file diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/res/layout/hello.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/res/layout/hello.xml new file mode 100644 index 000000000000..473f3f9f9402 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/res/layout/hello.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2025 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:keepScreenOn="true"> + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="this is a test activity" + /> +</LinearLayout> + diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java index 893f9cd01497..5d8d8081b0e5 100644 --- a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java +++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java @@ -19,6 +19,7 @@ package android.test.pagesizecompat; import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.view.View; import androidx.annotation.VisibleForTesting; @@ -43,6 +44,8 @@ public class MainActivity extends Activity { @Override public void onCreate(Bundle savedOnstanceState) { super.onCreate(savedOnstanceState); + View view = getLayoutInflater().inflate(R.layout.hello, null); + setContentView(view); Intent received = getIntent(); int op1 = received.getIntExtra(KEY_OPERAND_1, -1); diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java index 9cbe414a0993..7d05c64f7624 100644 --- a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java +++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java @@ -16,6 +16,8 @@ package android.test.pagesizecompat; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -23,6 +25,10 @@ import android.content.IntentFilter; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; import org.junit.Assert; import org.junit.Test; @@ -33,9 +39,10 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class PageSizeCompatTest { + private static final String WARNING_TEXT = "PageSizeCompatTestApp"; + private static final long TIMEOUT = 5000; - @Test - public void testPageSizeCompat_embedded4KbLib() throws Exception { + public void testPageSizeCompat_appLaunch(boolean shouldPass) throws Exception { Context context = InstrumentationRegistry.getContext(); CountDownLatch receivedSignal = new CountDownLatch(1); @@ -62,6 +69,30 @@ public class PageSizeCompatTest { launchIntent.putExtra(MainActivity.KEY_OPERAND_2, op2); context.startActivity(launchIntent); - Assert.assertTrue("Failed to launch app", receivedSignal.await(10, TimeUnit.SECONDS)); + UiDevice device = UiDevice.getInstance(getInstrumentation()); + device.waitForWindowUpdate(null, TIMEOUT); + + Assert.assertEquals(receivedSignal.await(10, TimeUnit.SECONDS), shouldPass); + } + + @Test + public void testPageSizeCompat_compatEnabled() throws Exception { + testPageSizeCompat_appLaunch(true); + } + + @Test + public void testPageSizeCompat_compatDisabled() throws Exception { + testPageSizeCompat_appLaunch(false); + } + + @Test + public void testPageSizeCompat_compatByAlignmentChecks() throws Exception { + testPageSizeCompat_appLaunch(true); + + //verify warning dialog + UiDevice device = UiDevice.getInstance(getInstrumentation()); + device.waitForWindowUpdate(null, TIMEOUT); + UiObject2 targetObject = device.wait(Until.findObject(By.text(WARNING_TEXT)), TIMEOUT); + Assert.assertTrue(targetObject != null); } } diff --git a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java index aed907a0242f..208d74e49afe 100644 --- a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java +++ b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java @@ -17,10 +17,12 @@ package com.android.internal.content; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import android.platform.test.annotations.AppModeFull; import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.targetprep.TargetSetupError; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; @@ -29,6 +31,12 @@ import org.junit.runner.RunWith; @RunWith(DeviceJUnit4ClassRunner.class) public class FileSystemUtilsTest extends BaseHostJUnit4Test { + private static final String PAGE_SIZE_COMPAT_ENABLED = "app_with_4kb_elf.apk"; + private static final String PAGE_SIZE_COMPAT_DISABLED = "page_size_compat_disabled_app.apk"; + private static final String PAGE_SIZE_COMPAT_ENABLED_COMPRESSED_ELF = + "app_with_4kb_compressed_elf.apk"; + private static final String PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM = + "app_with_4kb_elf_no_override.apk"; @Test @AppModeFull @@ -48,12 +56,50 @@ public class FileSystemUtilsTest extends BaseHostJUnit4Test { runDeviceTests(appPackage, appPackage + "." + testName); } - @Test - @AppModeFull - public void runAppWith4KbLib_overrideCompatMode() throws DeviceNotAvailableException { + private void runPageSizeCompatTest(String appName, String testMethodName) + throws DeviceNotAvailableException, TargetSetupError { + getDevice().enableAdbRoot(); + String result = getDevice().executeShellCommand("getconf PAGE_SIZE"); + assumeTrue("16384".equals(result.strip())); + installPackage(appName, "-r"); String appPackage = "android.test.pagesizecompat"; String testName = "PageSizeCompatTest"; assertTrue(isPackageInstalled(appPackage)); - runDeviceTests(appPackage, appPackage + "." + testName); + assertTrue(runDeviceTests(appPackage, appPackage + "." + testName, + testMethodName)); + uninstallPackage(appPackage); + } + + @Test + @AppModeFull + public void runAppWith4KbLib_overrideCompatMode() + throws DeviceNotAvailableException, TargetSetupError { + runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED, "testPageSizeCompat_compatEnabled"); + } + + @Test + @AppModeFull + public void runAppWith4KbCompressedLib_overrideCompatMode() + throws DeviceNotAvailableException, TargetSetupError { + runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED_COMPRESSED_ELF, + "testPageSizeCompat_compatEnabled"); + } + + @Test + @AppModeFull + public void runAppWith4KbLib_disabledCompatMode() + throws DeviceNotAvailableException, TargetSetupError { + // This test is expected to fail since compat is disabled in manifest + runPageSizeCompatTest(PAGE_SIZE_COMPAT_DISABLED, + "testPageSizeCompat_compatDisabled"); + } + + @Test + @AppModeFull + public void runAppWith4KbLib_compatByAlignmentChecks() + throws DeviceNotAvailableException, TargetSetupError { + // This test is expected to fail since compat is disabled in manifest + runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM, + "testPageSizeCompat_compatByAlignmentChecks"); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt index e55af112cd5f..0ea3c2a80fb4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DesktopModeCompatPolicy.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt @@ -14,20 +14,18 @@ * limitations under the License. */ -package com.android.wm.shell.compatui +package com.android.wm.shell.shared.desktopmode -import android.app.ActivityManager.RunningTaskInfo +import android.app.TaskInfo import android.content.Context +import android.window.DesktopModeFlags import com.android.internal.R -import com.android.wm.shell.dagger.WMSingleton -import javax.inject.Inject /** * Class to decide whether to apply app compat policies in desktop mode. */ // TODO(b/347289970): Consider replacing with API -@WMSingleton -class DesktopModeCompatPolicy @Inject constructor(context: Context) { +class DesktopModeCompatPolicy(context: Context) { private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi) @@ -37,16 +35,26 @@ class DesktopModeCompatPolicy @Inject constructor(context: Context) { * not being displayed, regardless of its configuration, we will not exempt it as to remain in * the desktop windowing environment. */ - fun isTopActivityExemptFromDesktopWindowing(task: RunningTaskInfo) = - (isSystemUiTask(task) || isTransparentTask(task)) && !task.isTopActivityNoDisplay + fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) = + isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName, + task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent) + + fun isTopActivityExemptFromDesktopWindowing(packageName: String?, + numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) = + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue + && ((isSystemUiTask(packageName) + || isTransparentTask(isActivityStackTransparent, numActivities)) + && !isTopActivityNoDisplay) /** * Returns true if all activities in a tasks stack are transparent. If there are no activities * will return false. */ - fun isTransparentTask(task: RunningTaskInfo): Boolean = task.isActivityStackTransparent - && task.numActivities > 0 + fun isTransparentTask(task: TaskInfo): Boolean = + isTransparentTask(task.isActivityStackTransparent, task.numActivities) + + private fun isTransparentTask(isActivityStackTransparent: Boolean, numActivities: Int) = + isActivityStackTransparent && numActivities > 0 - private fun isSystemUiTask(task: RunningTaskInfo): Boolean = - task.baseActivity?.packageName == systemUiPackage + private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index e68a98fb7f21..a65e69eee5fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -542,7 +542,7 @@ public class Bubble implements BubbleViewProvider { return (mMetadataShortcutId != null && !mMetadataShortcutId.isEmpty()); } - BubbleTransitions.BubbleTransition getPreparingTransition() { + public BubbleTransitions.BubbleTransition getPreparingTransition() { return mPreparingTransition; } @@ -572,7 +572,8 @@ public class Bubble implements BubbleViewProvider { mIntentActive = false; } - private void cleanupTaskView() { + /** Cleans-up the taskview associated with this bubble (possibly removing the task from wm) */ + public void cleanupTaskView() { if (mBubbleTaskView != null) { mBubbleTaskView.cleanup(); mBubbleTaskView = null; @@ -593,7 +594,7 @@ public class Bubble implements BubbleViewProvider { * <p>If we're switching between bar and floating modes, pass {@code false} on * {@code cleanupTaskView} to avoid recreating it in the new mode. */ - void cleanupViews(boolean cleanupTaskView) { + public void cleanupViews(boolean cleanupTaskView) { cleanupExpandedView(cleanupTaskView); mIconView = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 1f463295233f..5f2b95f7b137 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -40,9 +40,11 @@ import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.Notification; import android.app.NotificationChannel; import android.app.PendingIntent; +import android.app.TaskInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -78,6 +80,8 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.window.ScreenCapture; import android.window.ScreenCapture.SynchronousScreenCaptureListener; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; import androidx.annotation.MainThread; import androidx.annotation.Nullable; @@ -360,7 +364,7 @@ public class BubbleController implements ConfigurationChangeListener, } else { tvTransitions = taskViewTransitions; } - mTaskViewController = tvTransitions; + mTaskViewController = new BubbleTaskViewController(tvTransitions); mBubbleTransitions = new BubbleTransitions(transitions, organizer, taskViewRepository, data, tvTransitions, context); mTransitions = transitions; @@ -2076,7 +2080,12 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void removeBubble(Bubble removedBubble) { if (mLayerView != null) { + final BubbleTransitions.BubbleTransition bubbleTransit = + removedBubble.getPreparingTransition(); mLayerView.removeBubble(removedBubble, () -> { + if (bubbleTransit != null) { + bubbleTransit.continueCollapse(); + } if (!mBubbleData.hasBubbles() && !isStackExpanded()) { mLayerView.setVisibility(INVISIBLE); removeFromWindowManagerMaybe(); @@ -2702,7 +2711,18 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void collapseBubbles() { - mMainExecutor.execute(() -> mController.collapseStack()); + mMainExecutor.execute(() -> { + if (mBubbleData.getSelectedBubble() instanceof Bubble) { + if (((Bubble) mBubbleData.getSelectedBubble()).getPreparingTransition() + != null) { + // Currently preparing a transition which will, itself, collapse the bubble. + // For transition preparation, the timing of bubble-collapse must be in + // sync with the rest of the set-up. + return; + } + } + mController.collapseStack(); + }); } @Override @@ -3094,4 +3114,84 @@ public class BubbleController implements ConfigurationChangeListener, return mKeyToShownInShadeMap.get(key); } } + + private class BubbleTaskViewController implements TaskViewController { + private final TaskViewTransitions mBaseTransitions; + + BubbleTaskViewController(TaskViewTransitions baseTransitions) { + mBaseTransitions = baseTransitions; + } + + @Override + public void registerTaskView(TaskViewTaskController tv) { + mBaseTransitions.registerTaskView(tv); + } + + @Override + public void unregisterTaskView(TaskViewTaskController tv) { + mBaseTransitions.unregisterTaskView(tv); + } + + @Override + public void startShortcutActivity(@NonNull TaskViewTaskController destination, + @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options, + @Nullable Rect launchBounds) { + mBaseTransitions.startShortcutActivity(destination, shortcut, options, launchBounds); + } + + @Override + public void startActivity(@NonNull TaskViewTaskController destination, + @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, + @NonNull ActivityOptions options, @Nullable Rect launchBounds) { + mBaseTransitions.startActivity(destination, pendingIntent, fillInIntent, + options, launchBounds); + } + + @Override + public void startRootTask(@NonNull TaskViewTaskController destination, + ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, + @Nullable WindowContainerTransaction wct) { + mBaseTransitions.startRootTask(destination, taskInfo, leash, wct); + } + + @Override + public void removeTaskView(@NonNull TaskViewTaskController taskView, + @Nullable WindowContainerToken taskToken) { + mBaseTransitions.removeTaskView(taskView, taskToken); + } + + @Override + public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) { + final TaskInfo tinfo = taskView.getTaskInfo(); + if (tinfo == null) { + return; + } + Bubble bub = null; + for (Bubble b : mBubbleData.getBubbles()) { + if (b.getTaskId() == tinfo.taskId) { + bub = b; + break; + } + } + if (bub == null) { + return; + } + mBubbleTransitions.startConvertFromBubble(bub, tinfo); + } + + @Override + public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) { + mBaseTransitions.setTaskViewVisible(taskView, visible); + } + + @Override + public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) { + mBaseTransitions.setTaskBounds(taskView, boundsOnScreen); + } + + @Override + public boolean isUsingShellTransitions() { + return mBaseTransitions.isUsingShellTransitions(); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 1a61793eab87..a725e04d3f8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -87,6 +87,7 @@ public class BubblePositioner { private int mExpandedViewLargeScreenWidth; private int mExpandedViewLargeScreenInsetClosestEdge; private int mExpandedViewLargeScreenInsetFurthestEdge; + private int mExpandedViewBubbleBarWidth; private int mOverflowWidth; private int mExpandedViewPadding; @@ -158,12 +159,13 @@ public class BubblePositioner { mBubbleOffscreenAmount = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); + mExpandedViewBubbleBarWidth = Math.min( + res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width), + mPositionRect.width() - 2 * mExpandedViewPadding + ); if (mShowingInBubbleBar) { - mExpandedViewLargeScreenWidth = Math.min( - res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width), - mPositionRect.width() - 2 * mExpandedViewPadding - ); + mExpandedViewLargeScreenWidth = mExpandedViewBubbleBarWidth; } else if (mDeviceConfig.isSmallTablet()) { mExpandedViewLargeScreenWidth = (int) (bounds.width() * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT); @@ -888,7 +890,7 @@ public class BubblePositioner { * How wide the expanded view should be when showing from the bubble bar. */ public int getExpandedViewWidthForBubbleBar(boolean isOverflow) { - return isOverflow ? mOverflowWidth : mExpandedViewLargeScreenWidth; + return isOverflow ? mOverflowWidth : mExpandedViewBubbleBarWidth; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java index e37844f53b11..29fb1a23017c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java @@ -18,6 +18,8 @@ package com.android.wm.shell.bubbles; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.View.INVISIBLE; import static android.view.WindowManager.TRANSIT_CHANGE; import android.annotation.NonNull; @@ -30,8 +32,10 @@ import android.os.IBinder; import android.util.Slog; import android.view.SurfaceControl; import android.view.SurfaceView; +import android.view.View; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; @@ -53,6 +57,12 @@ import java.util.concurrent.Executor; public class BubbleTransitions { private static final String TAG = "BubbleTransitions"; + /** + * Multiplier used to convert a view elevation to an "equivalent" shadow-radius. This is the + * same multiple used by skia and surface-outsets in WMS. + */ + private static final float ELEVATION_TO_RADIUS = 2; + @NonNull final Transitions mTransitions; @NonNull final ShellTaskOrganizer mTaskOrganizer; @NonNull final TaskViewRepository mRepository; @@ -90,6 +100,44 @@ public class BubbleTransitions { } /** + * Starts a convert-from-bubble transition. + * + * @see ConvertFromBubble + */ + public BubbleTransition startConvertFromBubble(Bubble bubble, + TaskInfo taskInfo) { + ConvertFromBubble convert = new ConvertFromBubble(bubble, taskInfo); + return convert; + } + + /** + * Plucks the task-surface out of an ancestor view while making the view invisible. This helper + * attempts to do this seamlessly (ie. view becomes invisible in sync with task reparent). + */ + private void pluck(SurfaceControl taskLeash, View fromView, SurfaceControl dest, + float destX, float destY, float cornerRadius, SurfaceControl.Transaction t, + Runnable onPlucked) { + SurfaceControl.Transaction pluckT = new SurfaceControl.Transaction(); + pluckT.reparent(taskLeash, dest); + t.reparent(taskLeash, dest); + pluckT.setPosition(taskLeash, destX, destY); + t.setPosition(taskLeash, destX, destY); + pluckT.show(taskLeash); + pluckT.setAlpha(taskLeash, 1.f); + float shadowRadius = fromView.getElevation() * ELEVATION_TO_RADIUS; + pluckT.setShadowRadius(taskLeash, shadowRadius); + pluckT.setCornerRadius(taskLeash, cornerRadius); + t.setShadowRadius(taskLeash, shadowRadius); + t.setCornerRadius(taskLeash, cornerRadius); + + // Need to remove the taskview AFTER applying the startTransaction because it isn't + // synchronized. + pluckT.addTransactionCommittedListener(mMainExecutor, onPlucked::run); + fromView.getViewRootImpl().applyTransactionOnDraw(pluckT); + fromView.setVisibility(INVISIBLE); + } + + /** * Interface to a bubble-specific transition. Bubble transitions have a multi-step lifecycle * in order to coordinate with the bubble view logic. These steps are communicated on this * interface. @@ -98,6 +146,7 @@ public class BubbleTransitions { default void surfaceCreated() {} default void continueExpand() {} void skip(); + default void continueCollapse() {} } /** @@ -316,4 +365,154 @@ public class BubbleTransitions { } } } + + /** + * BubbleTransition that coordinates the setup for moving a task out of a bubble. The actual + * animation is owned by the "receiver" of the task; however, because Bubbles uses TaskView, + * we need to do some extra coordination work to get the task surface out of the view + * "seamlessly". + * + * The process here looks like: + * 1. Send transition to WM for leaving bubbles mode + * 2. in startAnimation, set-up a "pluck" operation to pull the task surface out of taskview + * 3. Once "plucked", remove the view (calls continueCollapse when surfaces can be cleaned-up) + * 4. Then re-dispatch the transition animation so that the "receiver" can animate it. + * + * So, constructor -> startAnimation -> continueCollapse -> re-dispatch. + */ + @VisibleForTesting + class ConvertFromBubble implements Transitions.TransitionHandler, BubbleTransition { + @NonNull final Bubble mBubble; + IBinder mTransition; + TaskInfo mTaskInfo; + SurfaceControl mTaskLeash; + SurfaceControl mRootLeash; + + ConvertFromBubble(@NonNull Bubble bubble, TaskInfo taskInfo) { + mBubble = bubble; + mTaskInfo = taskInfo; + + mBubble.setPreparingTransition(this); + WindowContainerTransaction wct = new WindowContainerTransaction(); + WindowContainerToken token = mTaskInfo.getToken(); + wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED); + wct.setAlwaysOnTop(token, false); + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false); + mTaskViewTransitions.enqueueExternal( + mBubble.getTaskView().getController(), + () -> { + mTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this); + return mTransition; + }); + } + + @Override + public void skip() { + mBubble.setPreparingTransition(null); + final TaskViewTaskController tv = + mBubble.getTaskView().getController(); + tv.notifyTaskRemovalStarted(tv.getTaskInfo()); + mTaskLeash = null; + } + + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @android.annotation.Nullable TransitionRequestInfo request) { + return null; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + } + + @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @NonNull SurfaceControl.Transaction finishTransaction) { + if (!aborted) return; + mTransition = null; + skip(); + mTaskViewTransitions.onExternalDone(transition); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mTransition != transition) return false; + + final TaskViewTaskController tv = + mBubble.getTaskView().getController(); + if (tv == null) { + mTaskViewTransitions.onExternalDone(transition); + return false; + } + + TransitionInfo.Change taskChg = null; + + boolean found = false; + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change chg = info.getChanges().get(i); + if (chg.getTaskInfo() == null) continue; + if (chg.getMode() != TRANSIT_CHANGE) continue; + if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue; + found = true; + mRepository.remove(tv); + taskChg = chg; + break; + } + + if (!found) { + Slog.w(TAG, "Expected a TaskView conversion in this transition but didn't get " + + "one, cleaning up the task view"); + tv.setTaskNotFound(); + skip(); + mTaskViewTransitions.onExternalDone(transition); + return false; + } + + mTaskLeash = taskChg.getLeash(); + mRootLeash = info.getRoot(0).getLeash(); + + SurfaceControl dest = + mBubble.getBubbleBarExpandedView().getViewRootImpl().getSurfaceControl(); + final Runnable onPlucked = () -> { + // Need to remove the taskview AFTER applying the startTransaction because + // it isn't synchronized. + tv.notifyTaskRemovalStarted(tv.getTaskInfo()); + // Unset after removeView so it can be used to pick a different animation. + mBubble.setPreparingTransition(null); + mBubbleData.setExpanded(false /* expanded */); + }; + if (dest != null) { + pluck(mTaskLeash, mBubble.getBubbleBarExpandedView(), dest, + taskChg.getStartAbsBounds().left - info.getRoot(0).getOffset().x, + taskChg.getStartAbsBounds().top - info.getRoot(0).getOffset().y, + mBubble.getBubbleBarExpandedView().getCornerRadius(), startTransaction, + onPlucked); + mBubble.getBubbleBarExpandedView().post(() -> mTransitions.dispatchTransition( + mTransition, info, startTransaction, finishTransaction, finishCallback, + null)); + } else { + onPlucked.run(); + mTransitions.dispatchTransition(mTransition, info, startTransaction, + finishTransaction, finishCallback, null); + } + + mTaskViewTransitions.onExternalDone(transition); + return true; + } + + @Override + public void continueCollapse() { + mBubble.cleanupTaskView(); + if (mTaskLeash == null) return; + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.reparent(mTaskLeash, mRootLeash); + t.apply(); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 91dcbdf5f117..f3f8d6f96a42 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -355,8 +355,10 @@ public class BubbleBarLayerView extends FrameLayout /** Removes the given {@code bubble}. */ public void removeBubble(Bubble bubble, Runnable endAction) { + final boolean inTransition = bubble.getPreparingTransition() != null; Runnable cleanUp = () -> { - bubble.cleanupViews(); + // The transition is already managing the task/wm state. + bubble.cleanupViews(!inTransition); endAction.run(); }; if (mBubbleData.getBubbles().isEmpty()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt index 4cd2fd04d3cf..ff3e65a247ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt @@ -15,16 +15,21 @@ */ package com.android.wm.shell.common +import android.annotation.UserIdInt import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.pm.LauncherApps import android.content.pm.PackageManager +import android.content.pm.PackageManager.Property import android.os.UserHandle import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL +import com.android.wm.shell.sysui.ShellCommandHandler +import com.android.wm.shell.sysui.ShellInit +import java.io.PrintWriter import java.util.Arrays /** @@ -35,12 +40,23 @@ class MultiInstanceHelper @JvmOverloads constructor( private val packageManager: PackageManager, private val staticAppsSupportingMultiInstance: Array<String> = context.resources .getStringArray(R.array.config_appsSupportMultiInstancesSplit), - private val supportsMultiInstanceProperty: Boolean) { + shellInit: ShellInit, + private val shellCommandHandler: ShellCommandHandler, + private val supportsMultiInstanceProperty: Boolean +) : ShellCommandHandler.ShellCommandActionHandler { + + init { + shellInit.addInitCallback(this::onInit, this) + } + + private fun onInit() { + shellCommandHandler.addCommandCallback("multi-instance", this, this) + } /** * Returns whether a specific component desires to be launched in multiple instances. */ - fun supportsMultiInstanceSplit(componentName: ComponentName?): Boolean { + fun supportsMultiInstanceSplit(componentName: ComponentName?, @UserIdInt userId: Int): Boolean { if (componentName == null || componentName.packageName == null) { // TODO(b/262864589): Handle empty component case return false @@ -63,8 +79,9 @@ class MultiInstanceHelper @JvmOverloads constructor( // Check the activity property first try { - val activityProp = packageManager.getProperty( - PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName) + val activityProp = packageManager.getPropertyAsUser( + PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName.packageName, + componentName.className, userId) // If the above call doesn't throw a NameNotFoundException, then the activity property // should override the application property value if (activityProp.isBoolean) { @@ -80,8 +97,9 @@ class MultiInstanceHelper @JvmOverloads constructor( // Check the application property otherwise try { - val appProp = packageManager.getProperty( - PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName) + val appProp = packageManager.getPropertyAsUser( + PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, null /* className */, + userId) if (appProp.isBoolean) { ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName) return appProp.boolean @@ -96,6 +114,66 @@ class MultiInstanceHelper @JvmOverloads constructor( return false } + override fun onShellCommand(args: Array<out String>?, pw: PrintWriter?): Boolean { + if (pw == null || args == null || args.isEmpty()) { + return false + } + when (args[0]) { + "list" -> return dumpSupportedApps(pw) + } + return false + } + + override fun printShellCommandHelp(pw: PrintWriter, prefix: String) { + pw.println("${prefix}list") + pw.println("$prefix Lists all the packages that support the multiinstance property") + } + + /** + * Dumps the static allowlist and list of apps that have the declared property in the manifest. + */ + private fun dumpSupportedApps(pw: PrintWriter): Boolean { + pw.println("Static allow list (for all users):") + staticAppsSupportingMultiInstance.forEach { pkg -> + pw.println(" $pkg") + } + + // TODO(b/391693747): Dump this per-user once PM allows us to query properties + // for non-calling users + val apps = packageManager.queryApplicationProperty( + PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI) + val activities = packageManager.queryActivityProperty( + PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI) + val appsWithProperty = (apps + activities) + .sortedWith(object : Comparator<Property?> { + override fun compare(o1: Property?, o2: Property?): Int { + if (o1?.packageName != o2?.packageName) { + return o1?.packageName!!.compareTo(o2?.packageName!!) + } else { + if (o1?.className != null) { + return o1.className!!.compareTo(o2?.className!!) + } else if (o2?.className != null) { + return -o2.className!!.compareTo(o1?.className!!) + } + return 0 + } + } + }) + if (appsWithProperty.isNotEmpty()) { + pw.println("Apps (User ${context.userId}):") + appsWithProperty.forEach { prop -> + if (prop.isBoolean && prop.boolean) { + if (prop.className != null) { + pw.println(" ${prop.packageName}/${prop.className}") + } else { + pw.println(" ${prop.packageName}") + } + } + } + } + return true + } + companion object { /** Returns the component from a PendingIntent */ @JvmStatic diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index dbf95742ca56..e0a829df79ad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -114,6 +114,7 @@ import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.annotations.ShellSplashscreenThread; +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -258,6 +259,12 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides + static DesktopModeCompatPolicy provideDesktopModeCompatPolicy(Context context) { + return new DesktopModeCompatPolicy(context); + } + + @WMSingleton + @Provides static Optional<CompatUIHandler> provideCompatUIController( Context context, ShellInit shellInit, @@ -410,9 +417,13 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static MultiInstanceHelper provideMultiInstanceHelper(Context context) { + static MultiInstanceHelper provideMultiInstanceHelper( + Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler + ) { return new MultiInstanceHelper(context, context.getPackageManager(), - Flags.supportsMultiInstanceSystemUi()); + shellInit, shellCommandHandler, Flags.supportsMultiInstanceSystemUi()); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index c71bf687374d..4bbe22a59315 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -37,6 +37,7 @@ import android.os.UserManager; import android.view.Choreographer; import android.view.IWindowManager; import android.view.WindowManager; +import android.window.DesktopModeFlags; import androidx.annotation.OptIn; @@ -72,7 +73,6 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.UserProfileContexts; import com.android.wm.shell.common.split.SplitState; -import com.android.wm.shell.compatui.DesktopModeCompatPolicy; import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler; import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver; import com.android.wm.shell.dagger.back.ShellBackAnimationModule; @@ -133,6 +133,7 @@ import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -814,7 +815,9 @@ public abstract class WMShellModule { ReturnToDragStartAnimator returnToDragStartAnimator, @DynamicOverride DesktopUserRepositories desktopUserRepositories, DesktopModeEventLogger desktopModeEventLogger, - WindowDecorTaskResourceLoader windowDecorTaskResourceLoader) { + WindowDecorTaskResourceLoader windowDecorTaskResourceLoader, + FocusTransitionObserver focusTransitionObserver, + @ShellMainThread ShellExecutor mainExecutor) { return new DesktopTilingDecorViewModel( context, mainDispatcher, @@ -828,7 +831,9 @@ public abstract class WMShellModule { returnToDragStartAnimator, desktopUserRepositories, desktopModeEventLogger, - windowDecorTaskResourceLoader + windowDecorTaskResourceLoader, + focusTransitionObserver, + mainExecutor ); } @@ -929,7 +934,7 @@ public abstract class WMShellModule { if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler() && manageKeyGestures() && (Flags.enableMoveToNextDisplayShortcut() - || Flags.enableTaskResizingKeyboardShortcuts())) { + || DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue())) { return Optional.of(new DesktopModeKeyGestureHandler(context, desktopModeWindowDecorViewModel, desktopTasksController, inputManager, shellTaskOrganizer, focusTransitionObserver, @@ -1210,7 +1215,8 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, IWindowManager windowManager, Optional<DesktopUserRepositories> desktopUserRepositories, - Optional<DesktopTasksController> desktopTasksController + Optional<DesktopTasksController> desktopTasksController, + ShellTaskOrganizer shellTaskOrganizer ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.empty(); @@ -1224,7 +1230,8 @@ public abstract class WMShellModule { rootTaskDisplayAreaOrganizer, windowManager, desktopUserRepositories.get(), - desktopTasksController.get())); + desktopTasksController.get(), + shellTaskOrganizer)); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 793bdf0b5614..413300612f7d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -159,11 +159,12 @@ public abstract class Pip2Module { PipUiEventLogger pipUiEventLogger, PipTaskListener pipTaskListener, @NonNull PipTransitionState pipTransitionState, + @NonNull PipDisplayLayoutState pipDisplayLayoutState, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return new PhonePipMenuController(context, pipBoundsState, pipMediaController, - systemWindows, pipUiEventLogger, pipTaskListener, pipTransitionState, mainExecutor, - mainHandler); + systemWindows, pipUiEventLogger, pipTaskListener, pipTransitionState, + pipDisplayLayoutState, mainExecutor, mainHandler); } @@ -178,6 +179,8 @@ public abstract class Pip2Module { @NonNull PipTransitionState pipTransitionState, @NonNull PipScheduler pipScheduler, @NonNull SizeSpecSource sizeSpecSource, + @NonNull PipDisplayLayoutState pipDisplayLayoutState, + DisplayController displayController, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, @@ -185,8 +188,9 @@ public abstract class Pip2Module { Optional<PipPerfHintController> pipPerfHintControllerOptional) { return new PipTouchHandler(context, shellInit, shellCommandHandler, menuPhoneController, pipBoundsAlgorithm, pipBoundsState, pipTransitionState, pipScheduler, - sizeSpecSource, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, - mainExecutor, pipPerfHintControllerOptional); + sizeSpecSource, pipDisplayLayoutState, displayController, pipMotionHelper, + floatingContentCoordinator, pipUiEventLogger, mainExecutor, + pipPerfHintControllerOptional); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt index 760d2124b845..6f455df6cfec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt @@ -16,7 +16,10 @@ package com.android.wm.shell.desktopmode +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED +import android.app.WindowConfiguration.windowingModeToString import android.content.Context import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS @@ -27,6 +30,7 @@ import android.window.WindowContainerTransaction import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener @@ -45,6 +49,7 @@ class DesktopDisplayEventHandler( private val windowManager: IWindowManager, private val desktopUserRepositories: DesktopUserRepositories, private val desktopTasksController: DesktopTasksController, + private val shellTaskOrganizer: ShellTaskOrganizer, ) : OnDisplaysChangedListener, OnDeskRemovedListener { private val desktopRepository: DesktopRepository @@ -72,6 +77,11 @@ class DesktopDisplayEventHandler( return } logV("Creating new desk in new display#$displayId") + // TODO: b/362720497 - when SystemUI crashes with a freeform task open for any reason, the + // task is recreated and received in [FreeformTaskListener] before this display callback + // is invoked, which results in the repository trying to add the task to a desk before the + // desk has been recreated here, which may result in a crash-loop if the repository is + // checking that the desk exists before adding a task to it. See b/391984373. desktopTasksController.createDesk(displayId) } @@ -119,13 +129,34 @@ class DesktopDisplayEventHandler( } val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY) requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." } - if (tdaInfo.configuration.windowConfiguration.windowingMode == targetDisplayWindowingMode) { + val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode + if (currentDisplayWindowingMode == targetDisplayWindowingMode) { // Already in the target mode. return } + logV( + "As an external display is connected, changing default display's windowing mode from" + + " ${windowingModeToString(currentDisplayWindowingMode)}" + + " to ${windowingModeToString(targetDisplayWindowingMode)}" + ) + val wct = WindowContainerTransaction() wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode) + shellTaskOrganizer + .getRunningTasks(DEFAULT_DISPLAY) + .filter { it.activityType == ACTIVITY_TYPE_STANDARD } + .forEach { + // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy + when (it.windowingMode) { + currentDisplayWindowingMode -> { + wct.setWindowingMode(it.token, currentDisplayWindowingMode) + } + targetDisplayWindowingMode -> { + wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED) + } + } + } transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 7897c0aa35bc..f5a95a670036 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -24,10 +24,10 @@ import android.view.MotionEvent import android.view.MotionEvent.TOOL_TYPE_FINGER import android.view.MotionEvent.TOOL_TYPE_MOUSE import android.view.MotionEvent.TOOL_TYPE_STYLUS +import android.window.DesktopModeFlags import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.internal.util.FrameworkStatsLog -import com.android.window.flags.Flags import com.android.wm.shell.EventLogTags import com.android.wm.shell.common.DisplayController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE @@ -185,7 +185,7 @@ class DesktopModeEventLogger { displayController: DisplayController? = null, displayLayoutSize: Size? = null, ) { - if (!Flags.enableResizingMetrics()) return + if (!DesktopModeFlags.ENABLE_RESIZING_METRICS.isTrue) return val sessionId = currentSessionId.get() if (sessionId == NO_SESSION_ID) { @@ -232,7 +232,7 @@ class DesktopModeEventLogger { displayController: DisplayController? = null, displayLayoutSize: Size? = null, ) { - if (!Flags.enableResizingMetrics()) return + if (!DesktopModeFlags.ENABLE_RESIZING_METRICS.isTrue) return val sessionId = currentSessionId.get() if (sessionId == NO_SESSION_ID) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt index 9334898fdb93..5269318943d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt @@ -23,10 +23,10 @@ import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventHandler import android.hardware.input.KeyGestureEvent import android.os.IBinder +import android.window.DesktopModeFlags import com.android.hardware.input.Flags.manageKeyGestures import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut -import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ShellExecutor @@ -144,7 +144,8 @@ class DesktopModeKeyGestureHandler( KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW, KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> - enableTaskResizingKeyboardShortcuts() && manageKeyGestures() + DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue && + manageKeyGestures() else -> false } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index 6636770895fa..4ff1a5f1be31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -926,7 +926,10 @@ class DesktopRepository( } override fun getDesk(deskId: Int): Desk = - checkNotNull(deskByDisplayId[deskId]) { "Expected desk $deskId to exist" } + // TODO: b/362720497 - consider enforcing that the desk has been created before trying + // to use it. As of now, there are cases where a task may be created faster than a + // desk is, so just create it here if needed. See b/391984373. + deskByDisplayId.getOrCreate(deskId) override fun getActiveDesk(displayId: Int): Desk { // TODO: 389787966 - consider migrating to an "active" state instead of checking the 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 883c988a79ca..ca71cf303a1c 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.annotation.UserIdInt import android.app.ActivityManager import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions @@ -86,7 +87,6 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.UserProfileContexts -import com.android.wm.shell.compatui.DesktopModeCompatPolicy import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -115,6 +115,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_ST import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity @@ -516,10 +517,7 @@ class DesktopTasksController( remoteTransition: RemoteTransition? = null, callback: IMoveToDesktopCallback? = null, ) { - if ( - DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && - desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(task) - ) { + if (desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(task)) { logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId) return } @@ -1821,8 +1819,7 @@ class DesktopTasksController( taskRepository.isActiveTask(triggerTask.taskId)) private fun isIncompatibleTask(task: RunningTaskInfo) = - DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && - desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(task) + desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(task) private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean = ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue() && @@ -2093,7 +2090,7 @@ class DesktopTasksController( */ private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { logV("handleIncompatibleTaskLaunch") - if (!isDesktopModeShowing(task.displayId)) return null + if (!isDesktopModeShowing(task.displayId) && !forceEnterDesktop(task.displayId)) return null // Only update task repository for transparent task. if ( DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC @@ -2745,6 +2742,7 @@ class DesktopTasksController( // TODO(b/358114479): Move this implementation into a separate class. override fun onUnhandledDrag( launchIntent: PendingIntent, + @UserIdInt userId: Int, dragEvent: DragEvent, onFinishCallback: Consumer<Boolean>, ): Boolean { @@ -2753,8 +2751,10 @@ class DesktopTasksController( // Not currently in desktop mode, ignore the drop return false } + + // TODO: val launchComponent = getComponent(launchIntent) - if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) { + if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent, userId)) { // TODO(b/320797628): Should only return early if there is an existing running task, and // notify the user as well. But for now, just ignore the drop. logV("Dropped intent does not support multi-instance") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt index b698040eca73..224ff37a1dca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt @@ -29,7 +29,6 @@ import androidx.core.animation.addListener import com.android.app.animation.Interpolators import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.compatui.DesktopModeCompatPolicy import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.DesktopWallpaperActivity import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE @@ -37,6 +36,7 @@ import com.android.wm.shell.shared.TransitionUtil.isClosingMode import com.android.wm.shell.shared.TransitionUtil.isClosingType import com.android.wm.shell.shared.TransitionUtil.isOpeningMode import com.android.wm.shell.shared.TransitionUtil.isOpeningType +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionHandler 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 e24b2c5f0134..e8996bc03eeb 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 @@ -31,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.PendingIntent; @@ -125,6 +126,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll * drag. */ default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent, + @UserIdInt int userId, @NonNull DragEvent dragEvent, @NonNull Consumer<Boolean> onFinishCallback) { return false; @@ -444,8 +446,10 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll return; } + // TODO(b/391624027): Consider piping through launch intent user if needed later + final int userId = launchIntent.getCreatorUserHandle().getIdentifier(); final boolean handled = notifyListeners( - l -> l.onUnhandledDrag(launchIntent, dragEvent, onFinishCallback)); + l -> l.onUnhandledDrag(launchIntent, userId, dragEvent, onFinishCallback)); if (!handled) { // Nobody handled this, we still have to notify WM onFinishCallback.accept(false); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 44900ce1db8a..65099c2dfb9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -38,6 +38,7 @@ import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipMediaController.ActionListener; import com.android.wm.shell.common.pip.PipMenuController; @@ -121,6 +122,9 @@ public class PhonePipMenuController implements PipMenuController, @NonNull private final PipTransitionState mPipTransitionState; + @NonNull + private final PipDisplayLayoutState mPipDisplayLayoutState; + private SurfaceControl mLeash; private ActionListener mMediaActionListener = new ActionListener() { @@ -134,7 +138,8 @@ public class PhonePipMenuController implements PipMenuController, public PhonePipMenuController(Context context, PipBoundsState pipBoundsState, PipMediaController mediaController, SystemWindows systemWindows, PipUiEventLogger pipUiEventLogger, PipTaskListener pipTaskListener, - @NonNull PipTransitionState pipTransitionState, ShellExecutor mainExecutor, + @NonNull PipTransitionState pipTransitionState, + @NonNull PipDisplayLayoutState pipDisplayLayoutState, ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; mPipBoundsState = pipBoundsState; @@ -142,6 +147,7 @@ public class PhonePipMenuController implements PipMenuController, mSystemWindows = systemWindows; mPipTaskListener = pipTaskListener; mPipTransitionState = pipTransitionState; + mPipDisplayLayoutState = pipDisplayLayoutState; mMainExecutor = mainExecutor; mMainHandler = mainHandler; mPipUiEventLogger = pipUiEventLogger; @@ -218,7 +224,7 @@ public class PhonePipMenuController implements PipMenuController, mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), - 0, SHELL_ROOT_LAYER_PIP); + mPipDisplayLayoutState.getDisplayId(), SHELL_ROOT_LAYER_PIP); setShellRootAccessibilityWindow(); // Make sure the initial actions are set diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index b1984ccef4cb..99c9302edb75 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -19,6 +19,7 @@ package com.android.wm.shell.pip2.phone; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; +import static android.view.Display.DEFAULT_DISPLAY; import android.annotation.NonNull; import android.app.ActivityManager; @@ -219,6 +220,7 @@ public class PipController implements ConfigurationChangeListener, mPipDisplayLayoutState.setDisplayLayout(layout); mDisplayController.addDisplayChangingController(this); + mDisplayController.addDisplayWindowListener(this); mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) { @Override @@ -297,6 +299,22 @@ public class PipController implements ConfigurationChangeListener, setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); } + @Override + public void onDisplayRemoved(int displayId) { + // If PiP was active on an external display that is removed, clean up states and set + // {@link PipDisplayLayoutState} to DEFAULT_DISPLAY. + if (Flags.enableConnectedDisplaysPip() && mPipTransitionState.isInPip() + && displayId == mPipDisplayLayoutState.getDisplayId() + && displayId != DEFAULT_DISPLAY) { + mPipTransitionState.setState(PipTransitionState.EXITING_PIP); + mPipTransitionState.setState(PipTransitionState.EXITED_PIP); + + mPipDisplayLayoutState.setDisplayId(DEFAULT_DISPLAY); + mPipDisplayLayoutState.setDisplayLayout( + mDisplayController.getDisplayLayout(DEFAULT_DISPLAY)); + } + } + /** * A callback for any observed transition that contains a display change in its * {@link android.window.TransitionRequestInfo}, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java index b3070f29c6e2..71697596afd3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.view.Display; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; @@ -34,7 +35,9 @@ import androidx.annotation.NonNull; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.DismissViewUtils; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.shared.bubbles.DismissCircleView; import com.android.wm.shell.shared.bubbles.DismissView; @@ -50,6 +53,9 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen /* The multiplier to apply scale the target size by when applying the magnetic field radius */ private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f; + /* The window type to apply to the display */ + private static final int WINDOW_TYPE = WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; + /** * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move * PIP. @@ -84,16 +90,22 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen private final Context mContext; private final PipMotionHelper mMotionHelper; private final PipUiEventLogger mPipUiEventLogger; - private final WindowManager mWindowManager; + private WindowManager mWindowManager; + /** The display id for the display that is associated with mWindowManager. */ + private int mWindowManagerDisplayId = -1; + private final PipDisplayLayoutState mPipDisplayLayoutState; + private final DisplayController mDisplayController; private final ShellExecutor mMainExecutor; public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger, - PipMotionHelper motionHelper, ShellExecutor mainExecutor) { + PipMotionHelper motionHelper, PipDisplayLayoutState pipDisplayLayoutState, + DisplayController displayController, ShellExecutor mainExecutor) { mContext = context; mPipUiEventLogger = pipUiEventLogger; mMotionHelper = motionHelper; + mPipDisplayLayoutState = pipDisplayLayoutState; + mDisplayController = displayController; mMainExecutor = mainExecutor; - mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } void init() { @@ -240,6 +252,8 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */ public void createOrUpdateDismissTarget() { + getWindowManager(); + if (mTargetViewContainer.getParent() == null) { mTargetViewContainer.cancelAnimators(); @@ -262,7 +276,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen WindowManager.LayoutParams.MATCH_PARENT, height, 0, windowSize.y - height, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WINDOW_TYPE, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, @@ -308,4 +322,16 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen mWindowManager.removeViewImmediate(mTargetViewContainer); } } + + /** Sets mWindowManager to WindowManager associated with the display where PiP is active on. */ + private void getWindowManager() { + final int pipDisplayId = mPipDisplayLayoutState.getDisplayId(); + if (mWindowManager != null && pipDisplayId == mWindowManagerDisplayId) { + return; + } + mWindowManagerDisplayId = pipDisplayId; + Display display = mDisplayController.getDisplay(mWindowManagerDisplayId); + Context uiContext = mContext.createWindowContext(display, WINDOW_TYPE, null); + mWindowManager = uiContext.getSystemService(WindowManager.class); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java index ffda56d89276..0a0ecffbea1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java @@ -16,8 +16,6 @@ package com.android.wm.shell.pip2.phone; -import static android.view.Display.DEFAULT_DISPLAY; - import android.os.Binder; import android.os.IBinder; import android.os.Looper; @@ -30,6 +28,7 @@ import android.view.InputEvent; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; @@ -84,6 +83,7 @@ public class PipInputConsumer { private final IWindowManager mWindowManager; private final IBinder mToken; private final String mName; + private final PipDisplayLayoutState mPipDisplayLayoutState; private final ShellExecutor mMainExecutor; private InputEventReceiver mInputEventReceiver; @@ -94,10 +94,11 @@ public class PipInputConsumer { * @param name the name corresponding to the input consumer that is defined in the system. */ public PipInputConsumer(IWindowManager windowManager, String name, - ShellExecutor mainExecutor) { + PipDisplayLayoutState pipDisplayLayoutState, ShellExecutor mainExecutor) { mWindowManager = windowManager; mToken = new Binder(); mName = name; + mPipDisplayLayoutState = pipDisplayLayoutState; mMainExecutor = mainExecutor; } @@ -138,9 +139,9 @@ public class PipInputConsumer { } final InputChannel inputChannel = new InputChannel(); try { - // TODO(b/113087003): Support Picture-in-picture in multi-display. - mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); - mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel); + final int displayId = mPipDisplayLayoutState.getDisplayId(); + mWindowManager.destroyInputConsumer(mToken, displayId); + mWindowManager.createInputConsumer(mToken, mName, displayId, inputChannel); } catch (RemoteException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Failed to create input consumer, %s", TAG, e); @@ -162,8 +163,7 @@ public class PipInputConsumer { return; } try { - // TODO(b/113087003): Support Picture-in-picture in multi-display. - mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); + mWindowManager.destroyInputConsumer(mToken, mPipDisplayLayoutState.getDisplayId()); } catch (RemoteException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Failed to destroy input consumer, %s", TAG, e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java index d98be55f28e1..e4be3f60f86e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -44,6 +44,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; @@ -70,9 +71,9 @@ public class PipResizeGestureHandler implements private final PipScheduler mPipScheduler; private final PipTransitionState mPipTransitionState; private final PhonePipMenuController mPhonePipMenuController; + private final PipDisplayLayoutState mPipDisplayLayoutState; private final PipUiEventLogger mPipUiEventLogger; private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; - private final int mDisplayId; private final ShellExecutor mMainExecutor; private final PointF mDownPoint = new PointF(); @@ -120,10 +121,10 @@ public class PipResizeGestureHandler implements PipTransitionState pipTransitionState, PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, + PipDisplayLayoutState pipDisplayLayoutState, ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) { mContext = context; - mDisplayId = context.getDisplayId(); mMainExecutor = mainExecutor; mPipPerfHintController = pipPerfHintController; mPipBoundsAlgorithm = pipBoundsAlgorithm; @@ -135,6 +136,7 @@ public class PipResizeGestureHandler implements mPipTransitionState.addPipTransitionStateChangedListener(this); mPhonePipMenuController = menuActivityController; + mPipDisplayLayoutState = pipDisplayLayoutState; mPipUiEventLogger = pipUiEventLogger; mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); } @@ -197,7 +199,7 @@ public class PipResizeGestureHandler implements if (mIsEnabled) { // Register input event receiver mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput( - "pip-resize", mDisplayId); + "pip-resize", mPipDisplayLayoutState.getDisplayId()); try { mMainExecutor.executeBlocking(() -> { mInputEventReceiver = new PipResizeInputEventReceiver( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index fc3fbe299605..35cd1a2e681f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -54,10 +54,12 @@ import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipDoubleTapHelper; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipUiEventLogger; @@ -91,6 +93,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha @NonNull private final PipTransitionState mPipTransitionState; @NonNull private final PipScheduler mPipScheduler; @NonNull private final SizeSpecSource mSizeSpecSource; + @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; private final ShellExecutor mMainExecutor; @@ -183,6 +186,8 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha @NonNull PipTransitionState pipTransitionState, @NonNull PipScheduler pipScheduler, @NonNull SizeSpecSource sizeSpecSource, + @NonNull PipDisplayLayoutState pipDisplayLayoutState, + DisplayController displayController, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, @@ -200,6 +205,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged); mPipScheduler = pipScheduler; mSizeSpecSource = sizeSpecSource; + mPipDisplayLayoutState = pipDisplayLayoutState; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; mFloatingContentCoordinator = floatingContentCoordinator; @@ -208,7 +214,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mMotionHelper = pipMotionHelper; mPipScheduler.setUpdateMovementBoundsRunnable(this::updateMovementBounds); mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger, - mMotionHelper, mainExecutor); + mMotionHelper, mPipDisplayLayoutState, displayController, mainExecutor); mTouchState = new PipTouchState(ViewConfiguration.get(context), () -> { mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL, @@ -220,8 +226,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mainExecutor); mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger, - menuController, mainExecutor, - mPipPerfHintController); + menuController, mPipDisplayLayoutState, mainExecutor, mPipPerfHintController); mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> { updateMinMaxSize(aspectRatio); onAspectRatioChanged(); @@ -264,7 +269,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mPipDismissTargetHandler.init(); mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), - INPUT_CONSUMER_PIP, mMainExecutor); + INPUT_CONSUMER_PIP, mPipDisplayLayoutState, mMainExecutor); mPipInputConsumer.setInputListener(this::handleTouchEvent); mPipInputConsumer.setRegistrationListener(this::onRegistrationChanged); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 272cb4372acf..03327bf463e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -70,6 +70,7 @@ import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.pip2.animation.PipEnterAnimator; import com.android.wm.shell.pip2.animation.PipExpandAnimator; @@ -115,6 +116,7 @@ public class PipTransition extends PipTransitionController implements private final PipTransitionState mPipTransitionState; private final PipDisplayLayoutState mPipDisplayLayoutState; private final DisplayController mDisplayController; + private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper; private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; private final Optional<DesktopWallpaperActivityTokenProvider> mDesktopWallpaperActivityTokenProviderOptional; @@ -171,6 +173,7 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState.addPipTransitionStateChangedListener(this); mPipDisplayLayoutState = pipDisplayLayoutState; mDisplayController = displayController; + mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext); mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; mDesktopWallpaperActivityTokenProviderOptional = desktopWallpaperActivityTokenProviderOptional; @@ -343,6 +346,25 @@ public class PipTransition extends PipTransitionController implements } } + @Override + public boolean syncPipSurfaceState(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + final TransitionInfo.Change pipChange = getPipChange(info); + if (pipChange == null) return false; + + // add shadow and corner radii + final SurfaceControl leash = pipChange.getLeash(); + final boolean isInPip = mPipTransitionState.isInPip(); + + mPipSurfaceTransactionHelper.round(startTransaction, leash, isInPip) + .shadow(startTransaction, leash, isInPip); + mPipSurfaceTransactionHelper.round(finishTransaction, leash, isInPip) + .shadow(finishTransaction, leash, isInPip); + + return true; + } + // // Animation schedulers and entry points // 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 99a89a6b884f..ae0159263364 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 @@ -649,11 +649,12 @@ public class SplitScreenController implements SplitDragPolicy.Starter, @Nullable Bundle options, UserHandle user) { if (options == null) options = new Bundle(); final ActivityOptions activityOptions = ActivityOptions.fromBundle(options); + final int userId = user.getIdentifier(); if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null), - user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) { + userId, getUserId(reverseSplitPosition(position), null))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit( - getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) { + getShortcutComponent(packageName, shortcutId, user, mLauncherApps), userId)) { activityOptions.setApplyMultipleTaskFlagForShortcut(true); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else if (isSplitScreenVisible()) { @@ -687,7 +688,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, final int userId1 = shortcutInfo.getUserId(); final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer); if (samePackage(packageName1, packageName2, userId1, userId2)) { - if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) { + if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity(), + userId1)) { activityOptions.setApplyMultipleTaskFlagForShortcut(true); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { @@ -735,7 +737,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer); boolean setSecondIntentMultipleTask = false; if (samePackage(packageName1, packageName2, userId1, userId2)) { - if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) { + if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent), + userId1)) { setSecondIntentMultipleTask = true; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { @@ -775,7 +778,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic(); boolean setSecondIntentMultipleTask = false; if (samePackage(packageName1, packageName2, userId1, userId2)) { - if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) { + if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1), + userId1)) { fillInIntent1 = new Intent(); fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); setSecondIntentMultipleTask = true; @@ -858,7 +862,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, return; } if (samePackage(packageName1, packageName2, userId1, userId2)) { - if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent))) { + if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent), userId1)) { // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of // the split and there is no reusable background task. fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index aef75e2dc99e..a0cc2bc8887b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -417,7 +417,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } } - void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) { + /** Notifies listeners of a task being removed. */ + public void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) { if (mListener == null) return; final int taskId = taskInfo.taskId; mListenerExecutor.execute(() -> mListener.onTaskRemovalStarted(taskId)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java index 7948eadb28f4..2b2cdf84005c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java @@ -16,13 +16,17 @@ package com.android.wm.shell.windowdecor; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; import android.content.Context; import android.hardware.input.InputManager; +import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; import android.view.InputDevice; +import android.view.InsetsState; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.SurfaceControl; @@ -33,6 +37,7 @@ import android.window.WindowContainerTransaction; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; @@ -49,7 +54,8 @@ import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSuppl * Works with decorations that extend {@link CarWindowDecoration}. */ public abstract class CarWindowDecorViewModel - implements WindowDecorViewModel, FocusTransitionListener { + implements WindowDecorViewModel, FocusTransitionListener, + DisplayInsetsController.OnInsetsChangedListener { private static final String TAG = "CarWindowDecorViewModel"; private final ShellTaskOrganizer mTaskOrganizer; @@ -57,31 +63,37 @@ public abstract class CarWindowDecorViewModel private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final ShellExecutor mMainExecutor; private final DisplayController mDisplayController; + private final DisplayInsetsController mDisplayInsetsController; private final FocusTransitionObserver mFocusTransitionObserver; private final SyncTransactionQueue mSyncQueue; private final SparseArray<CarWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier; + private final IActivityTaskManager mActivityTaskManager; public CarWindowDecorViewModel( Context context, + @ShellMainThread ShellExecutor mainExecutor, @ShellBackgroundThread ShellExecutor bgExecutor, - @ShellMainThread ShellExecutor shellExecutor, ShellInit shellInit, ShellTaskOrganizer taskOrganizer, DisplayController displayController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, FocusTransitionObserver focusTransitionObserver, WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) { mContext = context; - mMainExecutor = shellExecutor; + mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mTaskOrganizer = taskOrganizer; mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; mFocusTransitionObserver = focusTransitionObserver; mSyncQueue = syncQueue; mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; + mActivityTaskManager = ActivityTaskManager.getService(); shellInit.addInitCallback(this::onInit, this); + displayInsetsController.addGlobalInsetsChangedListener(this); } private void onInit() { @@ -187,6 +199,26 @@ public abstract class CarWindowDecorViewModel decoration.close(); } + @Override + public void insetsChanged(int displayId, InsetsState insetsState) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + try { + mActivityTaskManager.getTasks(/* maxNum= */ Integer.MAX_VALUE, + /* filterOnlyVisibleRecents= */ false, /* keepIntentExtra= */ false, + displayId) + .stream().filter(taskInfo -> taskInfo.isVisible && taskInfo.isRunning) + .forEach(taskInfo -> { + final CarWindowDecoration decoration = mWindowDecorByTaskId.get( + taskInfo.taskId); + if (decoration != null) { + decoration.relayout(taskInfo, t, t); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Cannot update decoration on inset change on displayId: " + displayId); + } + } + /** * @return {@code true} if the task/activity associated with {@code taskInfo} should show * window decoration. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java index 39437845301e..3182745d813e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java @@ -30,7 +30,6 @@ import android.view.WindowInsets; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -47,6 +46,7 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou private WindowDecorLinearLayout mRootView; private @ShellBackgroundThread final ShellExecutor mBgExecutor; private final View.OnClickListener mClickListener; + private final RelayoutParams mRelayoutParams = new RelayoutParams(); private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>(); CarWindowDecoration( @@ -75,7 +75,8 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou @SuppressLint("MissingPermission") void relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - relayout(taskInfo, startT, finishT, /* isCaptionVisible= */ true); + relayout(taskInfo, startT, finishT, + /* isCaptionVisible= */ mRelayoutParams.mIsCaptionVisible); } @SuppressLint("MissingPermission") @@ -84,12 +85,9 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou boolean isCaptionVisible) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - RelayoutParams relayoutParams = new RelayoutParams(); + updateRelayoutParams(mRelayoutParams, taskInfo, isCaptionVisible); - updateRelayoutParams(relayoutParams, taskInfo, - mDisplayController.getInsetsState(taskInfo.displayId), isCaptionVisible); - - relayout(relayoutParams, startT, finishT, wct, mRootView, mResult); + relayout(mRelayoutParams, startT, finishT, wct, mRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct)); @@ -118,7 +116,6 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou private void updateRelayoutParams( RelayoutParams relayoutParams, ActivityManager.RunningTaskInfo taskInfo, - @Nullable InsetsState displayInsetsState, boolean isCaptionVisible) { relayoutParams.reset(); relayoutParams.mRunningTaskInfo = taskInfo; @@ -127,16 +124,19 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou relayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; relayoutParams.mIsCaptionVisible = isCaptionVisible && mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded; - if (displayInsetsState != null) { - relayoutParams.mCaptionTopPadding = getTopPadding( - taskInfo.getConfiguration().windowConfiguration.getBounds(), - displayInsetsState); - } + relayoutParams.mCaptionTopPadding = getTopPadding(taskInfo, relayoutParams); + relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; relayoutParams.mApplyStartTransactionOnDraw = true; } - private static int getTopPadding(Rect taskBounds, @NonNull InsetsState insetsState) { + private int getTopPadding(ActivityManager.RunningTaskInfo taskInfo, + RelayoutParams relayoutParams) { + Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); + InsetsState insetsState = mDisplayController.getInsetsState(taskInfo.displayId); + if (insetsState == null) { + return relayoutParams.mCaptionTopPadding; + } Insets systemDecor = insetsState.calculateInsets(taskBounds, WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(), false /* ignoreVisibility */); 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 46c5048b2147..2cf574158358 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 @@ -108,7 +108,6 @@ import com.android.wm.shell.common.MultiInstanceHelper; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController; -import com.android.wm.shell.compatui.DesktopModeCompatPolicy; import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler; import com.android.wm.shell.desktopmode.DesktopImmersiveController; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; @@ -133,6 +132,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener; import com.android.wm.shell.shared.FocusTransitionListener; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; @@ -1657,8 +1657,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) { return false; } - if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() - && mDesktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) { + if (mDesktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) { return false; } if (isPartOfDefaultHomePackage(taskInfo)) { 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 dd7488e5f2f9..4e125d001076 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 @@ -1358,7 +1358,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mWebUri = assistContent == null ? null : AppToWebUtils.getSessionWebUri(assistContent); updateGenericLink(); final boolean supportsMultiInstance = mMultiInstanceHelper - .supportsMultiInstanceSplit(mTaskInfo.baseActivity) + .supportsMultiInstanceSplit(mTaskInfo.baseActivity, mTaskInfo.userId) && Flags.enableDesktopWindowingMultiInstanceFeatures(); final boolean shouldShowManageWindowsButton = supportsMultiInstance && mMinimumInstancesFound; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt index d72da3a08de5..8747f63e789f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt @@ -29,6 +29,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopTasksController @@ -37,6 +38,7 @@ import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader @@ -58,6 +60,8 @@ class DesktopTilingDecorViewModel( private val desktopUserRepositories: DesktopUserRepositories, private val desktopModeEventLogger: DesktopModeEventLogger, private val taskResourceLoader: WindowDecorTaskResourceLoader, + private val focusTransitionObserver: FocusTransitionObserver, + private val mainExecutor: ShellExecutor, ) : DisplayChangeController.OnDisplayChangingListener { @VisibleForTesting var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>() @@ -94,6 +98,8 @@ class DesktopTilingDecorViewModel( returnToDragStartAnimator, desktopUserRepositories, desktopModeEventLogger, + focusTransitionObserver, + mainExecutor, ) tilingTransitionHandlerByDisplayId.put(displayId, newHandler) newHandler @@ -112,9 +118,10 @@ class DesktopTilingDecorViewModel( } fun moveTaskToFrontIfTiled(taskInfo: RunningTaskInfo): Boolean { + // Always pass focus=true because taskInfo.isFocused is not updated yet. return tilingTransitionHandlerByDisplayId .get(taskInfo.displayId) - ?.moveTiledPairToFront(taskInfo, isTaskFocused = true) ?: false + ?.moveTiledPairToFront(taskInfo.taskId, isFocusedOnDisplay = true) ?: false } fun onOverviewAnimationStateChange(isRunning: Boolean) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index 6f2323347468..666d4bd046dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -35,11 +35,14 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import com.android.internal.annotations.VisibleForTesting import com.android.launcher3.icons.BaseIconFactory +import com.android.window.flags.Flags +import com.android.wm.shell.shared.FocusTransitionListener import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -49,6 +52,7 @@ import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration @@ -78,13 +82,16 @@ class DesktopTilingWindowDecoration( private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val desktopUserRepositories: DesktopUserRepositories, private val desktopModeEventLogger: DesktopModeEventLogger, + private val focusTransitionObserver: FocusTransitionObserver, + @ShellMainThread private val mainExecutor: ShellExecutor, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, ) : Transitions.TransitionHandler, ShellTaskOrganizer.FocusListener, ShellTaskOrganizer.TaskVanishedListener, DragEventListener, - Transitions.TransitionObserver { + Transitions.TransitionObserver, + FocusTransitionListener { companion object { private val TAG: String = DesktopTilingWindowDecoration::class.java.simpleName private const val TILING_DIVIDER_TAG = "Tiling Divider" @@ -176,8 +183,13 @@ class DesktopTilingWindowDecoration( if (!isTilingManagerInitialised) { desktopTilingDividerWindowManager = initTilingManagerForDisplay(displayId, config) isTilingManagerInitialised = true - shellTaskOrganizer.addFocusListener(this) - isTilingFocused = true + + if (Flags.enableDisplayFocusInShellTransitions()) { + focusTransitionObserver.setLocalFocusTransitionListener(this, mainExecutor) + } else { + shellTaskOrganizer.addFocusListener(this) + isTilingFocused = true + } } leftTaskResizingHelper?.initIfNeeded() rightTaskResizingHelper?.initIfNeeded() @@ -474,23 +486,33 @@ class DesktopTilingWindowDecoration( } } - // Only called if [taskInfo] relates to a focused task - private fun isTilingFocusRemoved(taskInfo: RunningTaskInfo): Boolean { + // Only called if [taskId] relates to a focused task + private fun isTilingFocusRemoved(taskId: Int): Boolean { return isTilingFocused && - taskInfo.taskId != leftTaskResizingHelper?.taskInfo?.taskId && - taskInfo.taskId != rightTaskResizingHelper?.taskInfo?.taskId + taskId != leftTaskResizingHelper?.taskInfo?.taskId && + taskId != rightTaskResizingHelper?.taskInfo?.taskId } + // Overriding ShellTaskOrganizer.FocusListener override fun onFocusTaskChanged(taskInfo: RunningTaskInfo?) { + if (Flags.enableDisplayFocusInShellTransitions()) return if (taskInfo != null) { - moveTiledPairToFront(taskInfo) + moveTiledPairToFront(taskInfo.taskId, taskInfo.isFocused) } } + // Overriding FocusTransitionListener + override fun onFocusedTaskChanged(taskId: Int, + isFocusedOnDisplay: Boolean, + isFocusedGlobally: Boolean) { + if (!Flags.enableDisplayFocusInShellTransitions()) return + moveTiledPairToFront(taskId, isFocusedOnDisplay) + } + // Only called if [taskInfo] relates to a focused task - private fun isTilingRefocused(taskInfo: RunningTaskInfo): Boolean { - return taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId || - taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId + private fun isTilingRefocused(taskId: Int): Boolean { + return taskId == leftTaskResizingHelper?.taskInfo?.taskId || + taskId == rightTaskResizingHelper?.taskInfo?.taskId } private fun buildTiledTasksMoveToFront(leftOnTop: Boolean): WindowContainerTransaction { @@ -582,14 +604,13 @@ class DesktopTilingWindowDecoration( * If specified, [isTaskFocused] will override [RunningTaskInfo.isFocused]. This is to be used * when called when the task will be focused, but the [taskInfo] hasn't been updated yet. */ - fun moveTiledPairToFront(taskInfo: RunningTaskInfo, isTaskFocused: Boolean? = null): Boolean { + fun moveTiledPairToFront(taskId: Int, isFocusedOnDisplay: Boolean): Boolean { if (!isTilingManagerInitialised) return false - val isFocused = isTaskFocused ?: taskInfo.isFocused - if (!isFocused) return false + if (!isFocusedOnDisplay) return false // If a task that isn't tiled is being focused, let the generic handler do the work. - if (isTilingFocusRemoved(taskInfo)) { + if (!Flags.enableDisplayFocusInShellTransitions() && isTilingFocusRemoved(taskId)) { isTilingFocused = false return false } @@ -597,31 +618,29 @@ class DesktopTilingWindowDecoration( val leftTiledTask = leftTaskResizingHelper ?: return false val rightTiledTask = rightTaskResizingHelper ?: return false if (!allTiledTasksVisible()) return false - val isLeftOnTop = taskInfo.taskId == leftTiledTask.taskInfo.taskId - if (isTilingRefocused(taskInfo)) { - val t = transactionSupplier.get() - isTilingFocused = true - if (taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId) { - desktopTilingDividerWindowManager?.onRelativeLeashChanged( - leftTiledTask.getLeash(), - t, - ) - } - if (taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId) { - desktopTilingDividerWindowManager?.onRelativeLeashChanged( - rightTiledTask.getLeash(), - t, - ) - } - transitions.startTransition( - TRANSIT_TO_FRONT, - buildTiledTasksMoveToFront(isLeftOnTop), - null, - ) - t.apply() - return true + val isLeftOnTop = taskId == leftTiledTask.taskInfo.taskId + if (!isTilingRefocused(taskId)) return false + val t = transactionSupplier.get() + if (!Flags.enableDisplayFocusInShellTransitions()) isTilingFocused = true + if (taskId == leftTaskResizingHelper?.taskInfo?.taskId) { + desktopTilingDividerWindowManager?.onRelativeLeashChanged( + leftTiledTask.getLeash(), + t, + ) } - return false + if (taskId == rightTaskResizingHelper?.taskInfo?.taskId) { + desktopTilingDividerWindowManager?.onRelativeLeashChanged( + rightTiledTask.getLeash(), + t, + ) + } + transitions.startTransition( + TRANSIT_TO_FRONT, + buildTiledTasksMoveToFront(isLeftOnTop), + null, + ) + t.apply() + return true } private fun allTiledTasksVisible(): Boolean { @@ -706,7 +725,13 @@ class DesktopTilingWindowDecoration( } private fun tearDownTiling() { - if (isTilingManagerInitialised) shellTaskOrganizer.removeFocusListener(this) + if (isTilingManagerInitialised) { + if (Flags.enableDisplayFocusInShellTransitions()) { + focusTransitionObserver.unsetLocalFocusTransitionListener(this) + } else { + shellTaskOrganizer.removeFocusListener(this) + } + } if (leftTaskResizingHelper == null && rightTaskResizingHelper == null) { shellTaskOrganizer.removeTaskVanishedListener(this) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt index 966aea3088c4..7855698d0151 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt @@ -21,6 +21,7 @@ import android.tools.NavBar import android.tools.Rotation import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.traces.parsers.WindowManagerStateHelper +import android.window.DesktopModeFlags import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation @@ -59,7 +60,7 @@ constructor( fun setup() { Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) if (usingKeyboard) { - Assume.assumeTrue(Flags.enableTaskResizingKeyboardShortcuts()) + Assume.assumeTrue(DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue) } tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt index 46c97b0a1397..2f99fbaba078 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt @@ -21,6 +21,7 @@ import android.tools.NavBar import android.tools.Rotation import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.traces.parsers.WindowManagerStateHelper +import android.window.DesktopModeFlags import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation @@ -62,7 +63,7 @@ constructor( Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) Assume.assumeTrue(Flags.enableMinimizeButton()) if (usingKeyboard) { - Assume.assumeTrue(Flags.enableTaskResizingKeyboardShortcuts()) + Assume.assumeTrue(DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue) } tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt index 068064402ee5..59d15ca4fa6b 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt @@ -20,6 +20,7 @@ import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation import android.tools.traces.parsers.WindowManagerStateHelper +import android.window.DesktopModeFlags import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation @@ -59,7 +60,7 @@ abstract class SnapResizeAppWindowWithKeyboardShortcuts( @Before fun setup() { Assume.assumeTrue(Flags.enableDesktopWindowingMode() && - Flags.enableTaskResizingKeyboardShortcuts() && tapl.isTablet) + DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue && tapl.isTablet) testApp.enterDesktopMode(wmHelper, device) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java index b4f514acf2dd..9d0ddbc6de12 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java @@ -179,4 +179,33 @@ public class BubbleTransitionsTest extends ShellTestCase { animCb.getValue().run(); assertTrue(finishCalled[0]); } + + @Test + public void testConvertFromBubble() { + ActivityManager.RunningTaskInfo taskInfo = setupBubble(); + final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertFromBubble( + mBubble, taskInfo); + final BubbleTransitions.ConvertFromBubble cfb = (BubbleTransitions.ConvertFromBubble) bt; + verify(mTransitions).startTransition(anyInt(), any(), eq(cfb)); + verify(mBubble).setPreparingTransition(eq(bt)); + assertTrue(mTaskViewTransitions.hasPending()); + + final TransitionInfo info = new TransitionInfo(TRANSIT_CHANGE, 0); + final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token, + mock(SurfaceControl.class)); + chg.setMode(TRANSIT_CHANGE); + chg.setTaskInfo(taskInfo); + info.addChange(chg); + info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0)); + SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + Transitions.TransitionFinishCallback finishCb = wct -> {}; + cfb.startAnimation(cfb.mTransition, info, startT, finishT, finishCb); + + // Can really only verify that it interfaces with the taskViewTransitions queue. + // The actual functioning of this is tightly-coupled with SurfaceFlinger and renderthread + // in order to properly synchronize surface manipulation with drawing and thus can't be + // directly tested. + assertFalse(mTaskViewTransitions.hasPending()); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt index bec91e910cf7..6b0c390ac239 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt @@ -33,8 +33,6 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.eq import org.mockito.kotlin.any -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -80,17 +78,19 @@ class MultiInstanceHelperTest : ShellTestCase() { @Test fun supportsMultiInstanceSplit_inStaticAllowList() { val allowList = arrayOf(TEST_PACKAGE) - val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true) + val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, + mock(), mock(), true) val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) - assertEquals(true, helper.supportsMultiInstanceSplit(component)) + assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test fun supportsMultiInstanceSplit_notInStaticAllowList() { val allowList = arrayOf(TEST_PACKAGE) - val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true) + val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, + mock(), mock(), true) val component = ComponentName(TEST_NOT_ALLOWED_PACKAGE, TEST_ACTIVITY) - assertEquals(false, helper.supportsMultiInstanceSplit(component)) + assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -99,17 +99,17 @@ class MultiInstanceHelperTest : ShellTestCase() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() val activityProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenReturn(activityProp) val appProp = PackageManager.Property("", false, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true) // Expect activity property to override application property - assertEquals(true, helper.supportsMultiInstanceSplit(component)) + assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -118,17 +118,17 @@ class MultiInstanceHelperTest : ShellTestCase() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() val activityProp = PackageManager.Property("", false, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenReturn(activityProp) val appProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true) // Expect activity property to override application property - assertEquals(false, helper.supportsMultiInstanceSplit(component)) + assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -136,17 +136,17 @@ class MultiInstanceHelperTest : ShellTestCase() { fun supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenThrow(PackageManager.NameNotFoundException()) val appProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true) // Expect fall through to app property - assertEquals(true, helper.supportsMultiInstanceSplit(component)) + assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -154,15 +154,15 @@ class MultiInstanceHelperTest : ShellTestCase() { fun supportsMultiInstanceSplit_noActivityOrAppProperty() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenThrow(PackageManager.NameNotFoundException()) - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenThrow(PackageManager.NameNotFoundException()) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) - assertEquals(false, helper.supportsMultiInstanceSplit(component)) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true) + assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -171,24 +171,25 @@ class MultiInstanceHelperTest : ShellTestCase() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() val activityProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenReturn(activityProp) val appProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), false) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), false) // Expect we only check the static list and not the property - assertEquals(false, helper.supportsMultiInstanceSplit(component)) + assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) verify(pm, never()).getProperty(any(), any<ComponentName>()) } companion object { val TEST_PACKAGE = "com.android.wm.shell.common" - val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake"; - val TEST_ACTIVITY = "TestActivity"; + val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake" + val TEST_ACTIVITY = "TestActivity" val TEST_SHORTCUT_ID = "test_shortcut_1" + val TEST_OTHER_USER_ID = 1234 } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt index 5d8d5a716504..fae7363e0676 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt @@ -16,8 +16,10 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.ContentResolver import android.os.Binder import android.platform.test.annotations.EnableFlags @@ -37,7 +39,9 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.window.flags.Flags import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.common.ShellExecutor @@ -81,12 +85,20 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories @Mock private lateinit var mockDesktopRepository: DesktopRepository @Mock private lateinit var mockDesktopTasksController: DesktopTasksController + @Mock private lateinit var shellTaskOrganizer: ShellTaskOrganizer private lateinit var mockitoSession: StaticMockitoSession private lateinit var shellInit: ShellInit private lateinit var handler: DesktopDisplayEventHandler private val onDisplaysChangedListenerCaptor = argumentCaptor<OnDisplaysChangedListener>() + private val runningTasks = mutableListOf<RunningTaskInfo>() + private val externalDisplayId = 100 + private val freeformTask = + TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build() + private val fullscreenTask = + TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build() + private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) @Before fun setUp() { @@ -99,8 +111,8 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { shellInit = spy(ShellInit(testExecutor)) whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository) whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } - val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) - whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) + .thenReturn(defaultTDA) handler = DesktopDisplayEventHandler( context, @@ -111,7 +123,11 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { mockWindowManager, mockDesktopUserRepositories, mockDesktopTasksController, + shellTaskOrganizer, ) + whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } + runningTasks.add(freeformTask) + runningTasks.add(fullscreenTask) shellInit.init() verify(displayController) .addDisplayWindowListener(onDisplaysChangedListenerCaptor.capture()) @@ -127,9 +143,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { extendedDisplayEnabled: Boolean, expectTransition: Boolean, ) { - val externalDisplayId = 100 - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode + defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode } val settingsSession = ExtendedDisplaySettingsSession( @@ -138,23 +152,17 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { ) settingsSession.use { - // The external display connected. - whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) - .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId)) - onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId) - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - // The external display disconnected. - whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) - .thenReturn(intArrayOf(DEFAULT_DISPLAY)) - onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId) + connectExternalDisplay() + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + disconnectExternalDisplay() if (expectTransition) { val arg = argumentCaptor<WindowContainerTransaction>() verify(transitions, times(2)) .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) - assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode) + assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) - assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode) + assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) .isEqualTo(defaultWindowingMode) } else { verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) @@ -227,6 +235,58 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY) } + @Test + fun displayWindowingModeSwitch_existingTasksOnConnected() { + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { + WINDOWING_MODE_FULLSCREEN + } + + ExtendedDisplaySettingsSession(context.contentResolver, 1).use { + connectExternalDisplay() + + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(1)) + .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } + } + + @Test + fun displayWindowingModeSwitch_existingTasksOnDisconnected() { + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { + WINDOWING_MODE_FULLSCREEN + } + + ExtendedDisplaySettingsSession(context.contentResolver, 1).use { + disconnectExternalDisplay() + + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(1)) + .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + } + + private fun connectExternalDisplay() { + whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId)) + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId) + } + + private fun disconnectExternalDisplay() { + whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) + .thenReturn(intArrayOf(DEFAULT_DISPLAY)) + onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId) + } + private class ExtendedDisplaySettingsSession( private val contentResolver: ContentResolver, private val overrideValue: Int, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index 5736793cd6a9..f5c93ee8ffe4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode import android.graphics.Rect +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.platform.test.flag.junit.SetFlagsRule @@ -1159,6 +1160,14 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse() } + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addTask_deskDoesNotExists_createsDesk() { + repo.addTask(displayId = 999, taskId = 6, isVisible = true) + + assertThat(repo.getActiveTaskIdsInDesk(999)).contains(6) + } + class TestListener : DesktopRepository.ActiveTasksListener { var activeChangesOnDefaultDisplay = 0 var activeChangesOnSecondaryDisplay = 0 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 774fd4e510d2..40c0e3610da2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -101,7 +101,6 @@ import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.UserProfileContexts -import com.android.wm.shell.compatui.DesktopModeCompatPolicy import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason @@ -130,6 +129,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN import com.android.wm.shell.shared.split.SplitScreenConstants @@ -3019,6 +3019,27 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() { + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + // Set task as systemUI package + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") + val task = + createFreeformTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = false + } + + assertThat(controller.isDesktopModeShowing(DEFAULT_DISPLAY)).isFalse() + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } + + @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() { val task = setUpFreeformTask() @@ -5155,7 +5176,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() whenever(mockDragEvent.dragSurface).thenReturn(dragSurface) whenever(mockDragEvent.x).thenReturn(inputCoordinate.x) whenever(mockDragEvent.y).thenReturn(inputCoordinate.y) - whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull())).thenReturn(true) + whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull(), anyInt())) + .thenReturn(true) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) doReturn(indicatorType) .whenever(spyController) @@ -5169,6 +5191,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() spyController.onUnhandledDrag( mockPendingIntent, + context.userId, mockDragEvent, mockCallback as Consumer<Boolean>, ) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt index b750684ae23c..dfb1b0c8c642 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt @@ -26,7 +26,6 @@ import android.view.WindowManager.TRANSIT_OPEN import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.compatui.DesktopModeCompatPolicy import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTaskBuilder @@ -34,6 +33,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTaskBuilder import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.DesktopWallpaperActivity +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt index e894297d5ea2..8c78debdc19f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/DesktopModeCompatPolicyTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt @@ -14,12 +14,13 @@ * limitations under the License. */ -package com.android.wm.shell.compatui +package com.android.wm.shell.shared.desktopmode import android.content.ComponentName import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.R +import com.android.wm.shell.compatui.CompatUIShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index bb9703fce2e3..7f6b06d46abf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -213,7 +213,7 @@ public class SplitScreenControllerTests extends ShellTestCase { @Test public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() { - doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any()); + doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any(), anyInt()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); @@ -252,13 +252,13 @@ public class SplitScreenControllerTests extends ShellTestCase { verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull(), eq(SPLIT_INDEX_0)); - verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any()); + verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any(), anyInt()); verify(mStageCoordinator, never()).switchSplitPosition(any()); } @Test public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() { - doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any()); + doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any(), anyInt()); doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = @@ -276,14 +276,14 @@ public class SplitScreenControllerTests extends ShellTestCase { mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, SPLIT_INDEX_0); - verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any()); + verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any(), anyInt()); verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull(), eq(SPLIT_INDEX_0)); } @Test public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() { - doReturn(false).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any()); + doReturn(false).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any(), anyInt()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt index 98529b5aa06d..c5c827467c75 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt @@ -55,7 +55,6 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.SyncTransactionQueue -import com.android.wm.shell.compatui.DesktopModeCompatPolicy import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler import com.android.wm.shell.desktopmode.DesktopImmersiveController import com.android.wm.shell.desktopmode.DesktopModeEventLogger @@ -70,6 +69,7 @@ import com.android.wm.shell.desktopmode.education.AppToWebEducationController import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController 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 9ea5fd6e1abe..87198d14c839 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 @@ -280,7 +280,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext = new TestableContext(mContext); mTestableContext.ensureTestableResources(); mContext.setMockPackageManager(mMockPackageManager); - when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())) + when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any(), anyInt())) .thenReturn(false); when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel"); final ActivityInfo activityInfo = createActivityInfo(); @@ -295,7 +295,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt(), anyInt())) .thenReturn(mMockHandleMenu); - when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); + when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any(), anyInt())) + .thenReturn(false); when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any())) .thenReturn(mMockAppHeaderViewHolder); when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt index 997ece6ecadc..2cabb9a33b86 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt @@ -23,6 +23,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopUserRepositories @@ -30,6 +31,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler +import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader @@ -67,6 +69,8 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val desktopModeWindowDecorationMock: DesktopModeWindowDecoration = mock() private val desktopTilingDecoration: DesktopTilingWindowDecoration = mock() private val taskResourceLoader: WindowDecorTaskResourceLoader = mock() + private val focusTransitionObserver: FocusTransitionObserver = mock() + private val mainExecutor: ShellExecutor = mock() private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel @Before @@ -86,6 +90,8 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { userRepositories, desktopModeEventLogger, taskResourceLoader, + focusTransitionObserver, + mainExecutor ) whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock) } @@ -140,7 +146,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { desktopTilingDecorViewModel.moveTaskToFrontIfTiled(task1) verify(desktopTilingDecoration, times(1)) - .moveTiledPairToFront(any(), isTaskFocused = eq(true)) + .moveTiledPairToFront(any(), isFocusedOnDisplay = eq(true)) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt index 2f15c2e38855..399a51e1ed08 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt @@ -33,6 +33,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -42,6 +43,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler +import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.DragResizeWindowGeometry @@ -105,6 +107,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private val mainDispatcher: MainCoroutineDispatcher = mock() private val bgScope: CoroutineScope = mock() private val taskResourceLoader: WindowDecorTaskResourceLoader = mock() + private val focusTransitionObserver: FocusTransitionObserver = mock() + private val mainExecutor: ShellExecutor = mock() private lateinit var tilingDecoration: DesktopTilingWindowDecoration private val split_divider_width = 10 @@ -129,6 +133,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { returnToDragStartAnimator, userRepositories, desktopModeEventLogger, + focusTransitionObserver, + mainExecutor ) whenever(context.createContextAsUser(any(), any())).thenReturn(context) whenever(userRepositories.current).thenReturn(desktopRepository) @@ -242,7 +248,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { BOUNDS, ) - assertThat(tilingDecoration.moveTiledPairToFront(task2)).isFalse() + assertThat(tilingDecoration.moveTiledPairToFront(task2.taskId, false)).isFalse() verify(transitions, never()).startTransition(any(), any(), any()) } @@ -272,7 +278,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { BOUNDS, ) - assertThat(tilingDecoration.moveTiledPairToFront(task3)).isFalse() + assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, false)).isFalse() verify(transitions, never()).startTransition(any(), any(), any()) } @@ -304,7 +310,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { ) task1.isFocused = true - assertThat(tilingDecoration.moveTiledPairToFront(task1, isTaskFocused = true)).isTrue() + assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue() verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null)) } @@ -336,8 +342,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { task1.isFocused = true task3.isFocused = true - assertThat(tilingDecoration.moveTiledPairToFront(task3)).isFalse() - assertThat(tilingDecoration.moveTiledPairToFront(task1)).isTrue() + assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, true)).isFalse() + assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, true)).isTrue() verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null)) } @@ -367,8 +373,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { BOUNDS, ) - assertThat(tilingDecoration.moveTiledPairToFront(task3, isTaskFocused = true)).isFalse() - assertThat(tilingDecoration.moveTiledPairToFront(task1, isTaskFocused = true)).isTrue() + assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, isFocusedOnDisplay = true)).isFalse() + assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue() verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null)) } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index aa3dbda374be..71013f7f4e34 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -22,6 +22,7 @@ import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; import static android.media.audio.Flags.cacheGetStreamMinMaxVolume; +import static android.media.audio.Flags.cacheGetStreamVolume; import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO; import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING; import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API; @@ -1241,7 +1242,12 @@ public class AudioManager { * @hide **/ public static final String VOLUME_MAX_CACHING_API = "getStreamMaxVolume"; - private static final int VOLUME_MIN_MAX_CACHING_SIZE = 16; + /** + * API string for caching the volume for each stream + * @hide + **/ + public static final String VOLUME_CACHING_API = "getStreamVolume"; + private static final int VOLUME_CACHING_SIZE = 16; private final IpcDataCache.QueryHandler<VolumeCacheQuery, Integer> mVolQuery = new IpcDataCache.QueryHandler<>() { @@ -1252,6 +1258,7 @@ public class AudioManager { return switch (query.queryCommand) { case QUERY_VOL_MIN -> service.getStreamMinVolume(query.stream); case QUERY_VOL_MAX -> service.getStreamMaxVolume(query.stream); + case QUERY_VOL -> service.getStreamVolume(query.stream); default -> { Log.w(TAG, "Not a valid volume cache query: " + query); yield null; @@ -1265,29 +1272,40 @@ public class AudioManager { }; private final IpcDataCache<VolumeCacheQuery, Integer> mVolMinCache = - new IpcDataCache<>(VOLUME_MIN_MAX_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + new IpcDataCache<>(VOLUME_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, VOLUME_MIN_CACHING_API, VOLUME_MIN_CACHING_API, mVolQuery); private final IpcDataCache<VolumeCacheQuery, Integer> mVolMaxCache = - new IpcDataCache<>(VOLUME_MIN_MAX_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + new IpcDataCache<>(VOLUME_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, VOLUME_MAX_CACHING_API, VOLUME_MAX_CACHING_API, mVolQuery); + private final IpcDataCache<VolumeCacheQuery, Integer> mVolCache = + new IpcDataCache<>(VOLUME_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + VOLUME_CACHING_API, VOLUME_CACHING_API, mVolQuery); + /** * Used to invalidate the cache for the given API * @hide **/ public static void clearVolumeCache(String api) { - if (cacheGetStreamMinMaxVolume()) { + if (cacheGetStreamMinMaxVolume() && (VOLUME_MAX_CACHING_API.equals(api) + || VOLUME_MIN_CACHING_API.equals(api))) { + IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, api); + } else if (cacheGetStreamVolume() && VOLUME_CACHING_API.equals(api)) { IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, api); + } else { + Log.w(TAG, "invalid clearVolumeCache for api " + api); } } private static final int QUERY_VOL_MIN = 1; private static final int QUERY_VOL_MAX = 2; + private static final int QUERY_VOL = 3; /** @hide */ @IntDef(prefix = "QUERY_VOL", value = { QUERY_VOL_MIN, - QUERY_VOL_MAX} + QUERY_VOL_MAX, + QUERY_VOL} ) @Retention(RetentionPolicy.SOURCE) private @interface QueryVolCommand {} @@ -1297,6 +1315,7 @@ public class AudioManager { return switch (queryCommand) { case QUERY_VOL_MIN -> "getStreamMinVolume"; case QUERY_VOL_MAX -> "getStreamMaxVolume"; + case QUERY_VOL -> "getStreamVolume"; default -> "invalid command"; }; } @@ -1373,6 +1392,9 @@ public class AudioManager { * @see #setStreamVolume(int, int, int) */ public int getStreamVolume(int streamType) { + if (cacheGetStreamVolume()) { + return mVolCache.query(new VolumeCacheQuery(streamType, QUERY_VOL)); + } final IAudioService service = getService(); try { return service.getStreamVolume(streamType); diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java index 8ae72de3214a..e558209420e0 100644 --- a/media/java/android/media/quality/MediaQualityContract.java +++ b/media/java/android/media/quality/MediaQualityContract.java @@ -385,7 +385,6 @@ public class MediaQualityContract { public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED = "auto_super_resolution_enabled"; - /** * @hide * @@ -709,12 +708,6 @@ public class MediaQualityContract { * @hide * */ - public static final String PARAMETER_ACTIVE_PROFILE = "active_profile"; - - /** - * @hide - * - */ public static final String PARAMETER_PICTURE_QUALITY_EVENT_TYPE = "picture_quality_event_type"; @@ -977,11 +970,6 @@ public class MediaQualityContract { /** * @hide */ - public static final String PARAMETER_ACTIVE_PROFILE = "active_profile"; - - /** - * @hide - */ public static final String PARAMETER_SOUND_STYLE = "sound_style"; diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java index 017a1029d35c..209734ca4a53 100644 --- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java @@ -67,6 +67,17 @@ public class AudioManagerTest { private AudioManager mAudioManager; + private static final int[] PUBLIC_STREAM_TYPES = { + STREAM_VOICE_CALL, + STREAM_SYSTEM, + STREAM_RING, + STREAM_MUSIC, + STREAM_ALARM, + STREAM_NOTIFICATION, + STREAM_DTMF, + STREAM_ACCESSIBILITY, + }; + @Rule public final AudioVolumesTestRule rule = new AudioVolumesTestRule(); @@ -226,18 +237,8 @@ public class AudioManagerTest { public void getStreamMinMaxVolume_consistentWithAs() throws Exception { IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); IAudioService service = IAudioService.Stub.asInterface(b); - final int[] streamTypes = { - STREAM_VOICE_CALL, - STREAM_SYSTEM, - STREAM_RING, - STREAM_MUSIC, - STREAM_ALARM, - STREAM_NOTIFICATION, - STREAM_DTMF, - STREAM_ACCESSIBILITY, - }; - - for (int streamType : streamTypes) { + + for (int streamType : PUBLIC_STREAM_TYPES) { assertEquals(service.getStreamMinVolume(streamType), mAudioManager.getStreamMinVolume(streamType)); assertEquals(service.getStreamMaxVolume(streamType), @@ -245,6 +246,17 @@ public class AudioManagerTest { } } + @Test + public void getStreamVolume_consistentWithAs() throws Exception { + IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); + IAudioService service = IAudioService.Stub.asInterface(b); + + for (int streamType : PUBLIC_STREAM_TYPES) { + assertEquals(service.getStreamVolume(streamType), + mAudioManager.getStreamVolume(streamType)); + } + } + //----------------------------------------------------------------- // Test Volume per Attributes setter/getters //----------------------------------------------------------------- diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml index f55b320269a8..ff22b2e7f86f 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml index b663b6ccc5bf..d878ba0d310b 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml index 784e6ad6a9f8..8f0a158c55eb 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml index 8b44a6539801..0c8996063e9f 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml index f8a2d8fbd975..41d8490feeb3 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml index 781a5a136164..958552064c98 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml index 5b568f870ea4..03ca1f0a1033 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml index 1e7a08b714f1..030ee66fef3f 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml index 42116be07041..5c16723f7a63 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml index 1ff09901ffaf..5405045a013d 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <Button android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml index fa13b4125065..b23c5a510745 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml @@ -29,7 +29,8 @@ android:layout_height="wrap_content" android:paddingVertical="@dimen/settingslib_expressive_space_small1" android:paddingHorizontal="@dimen/settingslib_expressive_space_small4" - android:background="@drawable/settingslib_number_button_background"> + android:background="@drawable/settingslib_number_button_background" + android:filterTouchesWhenObscured="true"> <TextView android:id="@+id/settingslib_number_count" android:layout_width="wrap_content" diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml index e7fb572d06dc..66a4c2e25c77 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml @@ -21,7 +21,8 @@ android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4"> + android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_section_button" diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java index c36ade979d47..d3219c287f4b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java @@ -41,10 +41,23 @@ public class RestrictedDropDownPreference extends DropDownPreference implements * package. Marks the preference as disabled if so. * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName, boolean settingEnabled) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled); + } + + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version + * @param settingIdentifier The key identifying the setting + * @param packageName the package to check the settingIdentifier for */ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java index 332042a5c4f9..ec1b2b3e2589 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java @@ -107,12 +107,25 @@ public class RestrictedPreference extends TwoTargetPreference implements /** * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this * package. Marks the preference as disabled if so. + * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for */ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false); + } + + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param settingIdentifier The key identifying the setting + * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName, boolean settingEnabled) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 25628fba1b66..212e43aa4044 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -204,16 +204,33 @@ public class RestrictedPreferenceHelper { * package. Marks the preference as disabled if so. * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled */ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, - @NonNull String packageName) { + @NonNull String packageName, boolean settingEnabled) { updatePackageDetails(packageName, android.os.Process.INVALID_UID); + if (settingEnabled) { + setDisabledByEcm(null); + return; + } Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation( mContext, settingIdentifier, packageName); setDisabledByEcm(intent); } /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version + * @param settingIdentifier The key identifying the setting + * @param packageName the package to check the settingIdentifier for + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName) { + checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false); + } + + /** * @return EnforcedAdmin if we have been passed the restriction in the xml. */ public EnforcedAdmin checkRestrictionEnforced() { diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java index 573869db5073..f8f16a9dd63b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java @@ -134,10 +134,11 @@ public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPr * * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled */ - public void checkEcmRestrictionAndSetDisabled( - @NonNull String settingIdentifier, @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName, boolean settingEnabled) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index 0aac9a1104e9..a5fa6a854e97 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -220,10 +220,23 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement * package. Marks the preference as disabled if so. * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled */ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, - @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); + @NonNull String packageName, boolean settingEnabled) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled); + } + + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version + * @param settingIdentifier The key identifying the setting + * @param packageName the package to check the settingIdentifier for + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 68e9fe703090..a00484ac28ab 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -1169,4 +1169,38 @@ public class BluetoothUtils { String metadataValue = getFastPairCustomizedField(bluetoothDevice, TEMP_BOND_TYPE); return Objects.equals(metadataValue, TEMP_BOND_DEVICE_METADATA_VALUE); } + + /** + * Set temp bond metadata to device + * + * @param device the BluetoothDevice to be marked as temp bond + * + * Note: It is a workaround since Bluetooth API is not ready. + * Avoid using this method if possible + */ + public static void setTemporaryBondMetadata(@Nullable BluetoothDevice device) { + if (device == null) return; + if (!Flags.enableTemporaryBondDevicesUi()) { + Log.d(TAG, "Skip setTemporaryBondMetadata, flag is disabled"); + return; + } + String fastPairCustomizedMeta = getStringMetaData(device, + METADATA_FAST_PAIR_CUSTOMIZED_FIELDS); + String fullContentWithTag = generateExpressionWithTag(TEMP_BOND_TYPE, + TEMP_BOND_DEVICE_METADATA_VALUE); + if (TextUtils.isEmpty(fastPairCustomizedMeta)) { + fastPairCustomizedMeta = fullContentWithTag; + } else { + String oldValue = extraTagValue(TEMP_BOND_TYPE, fastPairCustomizedMeta); + if (TextUtils.isEmpty(oldValue)) { + fastPairCustomizedMeta += fullContentWithTag; + } else { + fastPairCustomizedMeta = + fastPairCustomizedMeta.replace( + generateExpressionWithTag(TEMP_BOND_TYPE, oldValue), + fullContentWithTag); + } + } + device.setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, fastPairCustomizedMeta.getBytes()); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java index e3d7902f34b2..00973811dbf0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java @@ -84,7 +84,11 @@ public final class ActionDisabledByAdminControllerFactory { return false; } DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); - return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm, + final SupervisionManager sm = + android.app.supervision.flags.Flags.deprecateDpmSupervisionApis() + ? context.getSystemService(SupervisionManager.class) + : null; + return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm, sm, BiometricAuthenticator.TYPE_ANY_BIOMETRIC, new UserHandle(UserHandle.myUserId())); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index cafe19ff9a9b..7c46db96595f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -22,9 +22,11 @@ import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANC import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,6 +46,8 @@ import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.net.Uri; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.util.Pair; @@ -109,6 +113,7 @@ public class BluetoothUtilsTest { + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>"; private static final String TEMP_BOND_METADATA = "<TEMP_BOND_TYPE>" + LE_AUDIO_SHARING_METADATA + "</TEMP_BOND_TYPE>"; + private static final String FAKE_TEMP_BOND_METADATA = "<TEMP_BOND_TYPE>fake</TEMP_BOND_TYPE>"; private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager"; private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component"; private static final int TEST_BROADCAST_ID = 25; @@ -1348,4 +1353,34 @@ public class BluetoothUtilsTest { assertThat(BluetoothUtils.isTemporaryBondDevice(mBluetoothDevice)).isTrue(); } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) + public void setTemporaryBondDevice_flagOff_doNothing() { + when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)) + .thenReturn(new byte[]{}); + BluetoothUtils.setTemporaryBondMetadata(mBluetoothDevice); + verify(mBluetoothDevice, never()).setMetadata(eq(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS), + any()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) + public void setTemporaryBondDevice_flagOn_setCorrectValue() { + when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)) + .thenReturn(new byte[]{}); + BluetoothUtils.setTemporaryBondMetadata(mBluetoothDevice); + verify(mBluetoothDevice).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, + TEMP_BOND_METADATA.getBytes()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) + public void setTemporaryBondDevice_flagOff_replaceAndSetCorrectValue() { + when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)) + .thenReturn(FAKE_TEMP_BOND_METADATA.getBytes()); + BluetoothUtils.setTemporaryBondMetadata(mBluetoothDevice); + verify(mBluetoothDevice).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, + TEMP_BOND_METADATA.getBytes()); + } } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 7cf83135c237..a92df8026715 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -237,6 +237,13 @@ flag { } flag { + name: "notification_bundle_ui" + namespace: "systemui" + description: "Three-level group UI for notification bundles" + bug: "367996732" +} + +flag { name: "notification_background_tint_optimization" namespace: "systemui" description: "Re-enable the codepath that removed tinting of notifications when the" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt index 78ae4af258fc..9545bda80b2d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt @@ -1,9 +1,12 @@ package com.android.systemui.animation -private const val TAG_WGHT = "wght" -private const val TAG_WDTH = "wdth" -private const val TAG_OPSZ = "opsz" -private const val TAG_ROND = "ROND" +object GSFAxes { + const val WEIGHT = "wght" + const val WIDTH = "wdth" + const val SLANT = "slnt" + const val ROUND = "ROND" + const val OPTICAL_SIZE = "opsz" +} class FontVariationUtils { private var mWeight = -1 @@ -21,7 +24,7 @@ class FontVariationUtils { weight: Int = -1, width: Int = -1, opticalSize: Int = -1, - roundness: Int = -1 + roundness: Int = -1, ): String { isUpdated = false if (weight >= 0 && mWeight != weight) { @@ -43,16 +46,20 @@ class FontVariationUtils { } var resultString = "" if (mWeight >= 0) { - resultString += "'$TAG_WGHT' $mWeight" + resultString += "'${GSFAxes.WEIGHT}' $mWeight" } if (mWidth >= 0) { - resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_WDTH' $mWidth" + resultString += + (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH}' $mWidth" } if (mOpticalSize >= 0) { - resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_OPSZ' $mOpticalSize" + resultString += + (if (resultString.isBlank()) "" else ", ") + + "'${GSFAxes.OPTICAL_SIZE}' $mOpticalSize" } if (mRoundness >= 0) { - resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_ROND' $mRoundness" + resultString += + (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND}' $mRoundness" } return if (isUpdated) resultString else "" } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index f6ff3268fca4..36029177d4f6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -17,6 +17,7 @@ import android.content.Context import android.content.res.Resources import android.graphics.Typeface import android.view.LayoutInflater +import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.ClockController @@ -107,18 +108,18 @@ class DefaultClockProvider( // TODO(b/364681643): Variations for retargetted DIGITAL_CLOCK_FLEX val LEGACY_FLEX_LS_VARIATION = listOf( - ClockFontAxisSetting("wght", 600f), - ClockFontAxisSetting("wdth", 100f), - ClockFontAxisSetting("ROND", 100f), - ClockFontAxisSetting("slnt", 0f), + ClockFontAxisSetting(GSFAxes.WEIGHT, 600f), + ClockFontAxisSetting(GSFAxes.WIDTH, 100f), + ClockFontAxisSetting(GSFAxes.ROUND, 100f), + ClockFontAxisSetting(GSFAxes.SLANT, 0f), ) val LEGACY_FLEX_AOD_VARIATION = listOf( - ClockFontAxisSetting("wght", 74f), - ClockFontAxisSetting("wdth", 43f), - ClockFontAxisSetting("ROND", 100f), - ClockFontAxisSetting("slnt", 0f), + ClockFontAxisSetting(GSFAxes.WEIGHT, 74f), + ClockFontAxisSetting(GSFAxes.WIDTH, 43f), + ClockFontAxisSetting(GSFAxes.ROUND, 100f), + ClockFontAxisSetting(GSFAxes.SLANT, 0f), ) val FLEX_TYPEFACE by lazy { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt index b2bc9ef4e363..cc3769e0a568 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt @@ -16,6 +16,7 @@ package com.android.systemui.shared.clocks +import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.AxisType @@ -122,7 +123,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController val FONT_AXES = listOf( ClockFontAxis( - key = "wght", + key = GSFAxes.WEIGHT, type = AxisType.Float, minValue = 25f, currentValue = 400f, @@ -131,7 +132,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController description = "Glyph Weight", ), ClockFontAxis( - key = "wdth", + key = GSFAxes.WIDTH, type = AxisType.Float, minValue = 25f, currentValue = 100f, @@ -140,7 +141,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController description = "Glyph Width", ), ClockFontAxis( - key = "ROND", + key = GSFAxes.ROUND, type = AxisType.Boolean, minValue = 0f, currentValue = 0f, @@ -149,7 +150,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController description = "Glyph Roundness", ), ClockFontAxis( - key = "slnt", + key = GSFAxes.SLANT, type = AxisType.Boolean, minValue = 0f, currentValue = 0f, diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt index 67cbf3082632..e2bbe0fef3c0 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt @@ -21,6 +21,7 @@ import android.view.Gravity import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout +import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations @@ -130,7 +131,7 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: if (!isLargeClock) { axes = axes.map { axis -> - if (axis.key == "wdth" && axis.value > SMALL_CLOCK_MAX_WDTH) { + if (axis.key == GSFAxes.WIDTH && axis.value > SMALL_CLOCK_MAX_WDTH) { axis.copy(value = SMALL_CLOCK_MAX_WDTH) } else { axis diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt index c40bb9a5ebea..3eb519186a3e 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt @@ -139,13 +139,49 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) { digitalClockTextViewMap.forEach { (_, textView) -> textView.refreshText() } } + override fun setVisibility(visibility: Int) { + if (visibility != this.visibility) { + logger.d({ "setVisibility(${str1 ?: int1})" }) { + int1 = visibility + str1 = + when (visibility) { + VISIBLE -> "VISIBLE" + INVISIBLE -> "INVISIBLE" + GONE -> "GONE" + else -> null + } + } + } + + super.setVisibility(visibility) + } + + private var loggedAlpha = 1000f + + override fun setAlpha(alpha: Float) { + val delta = if (alpha <= 0f || alpha >= 1f) 0.001f else 0.5f + if (abs(loggedAlpha - alpha) >= delta) { + loggedAlpha = alpha + logger.d({ "setAlpha($double1)" }) { double1 = alpha.toDouble() } + } + super.setAlpha(alpha) + } + + private val isDrawn: Boolean + get() = (mPrivateFlags and 0x20 /* PFLAG_DRAWN */) > 0 + override fun invalidate() { - logger.d("invalidate()") + if (isDrawn && visibility == VISIBLE) { + logger.d("invalidate()") + } + super.invalidate() } override fun requestLayout() { - logger.d("requestLayout()") + if (!isLayoutRequested()) { + logger.d("requestLayout()") + } super.requestLayout() } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 2b0825f39243..fbd5887c5b54 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -34,6 +34,7 @@ import android.view.View.MeasureSpec.EXACTLY import android.view.animation.Interpolator import android.widget.TextView import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.animation.GSFAxes import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R import com.android.systemui.log.core.Logger @@ -44,6 +45,7 @@ import com.android.systemui.shared.clocks.DimensionParser import com.android.systemui.shared.clocks.FontTextStyle import com.android.systemui.shared.clocks.LogUtil import java.lang.Thread +import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -206,7 +208,10 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe } override fun onDraw(canvas: Canvas) { - logger.d({ "onDraw(); ls: $str1" }) { str1 = textAnimator.textInterpolator.shapedText } + logger.d({ "onDraw(${str1?.replace("\n", "\\n")})" }) { + str1 = textAnimator.textInterpolator.shapedText + } + val translation = getLocalTranslation() canvas.translate(translation.x.toFloat(), translation.y.toFloat()) digitTranslateAnimator?.let { @@ -221,8 +226,42 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe canvas.translate(-translation.x.toFloat(), -translation.y.toFloat()) } + override fun setVisibility(visibility: Int) { + if (visibility != this.visibility) { + logger.d({ "setVisibility(${str1 ?: int1})" }) { + int1 = visibility + str1 = + when (visibility) { + VISIBLE -> "VISIBLE" + INVISIBLE -> "INVISIBLE" + GONE -> "GONE" + else -> null + } + } + } + + super.setVisibility(visibility) + } + + private var loggedAlpha = 1000f + + override fun setAlpha(alpha: Float) { + val delta = if (alpha <= 0f || alpha >= 1f) 0.001f else 0.5f + if (abs(loggedAlpha - alpha) >= delta) { + loggedAlpha = alpha + logger.d({ "setAlpha($double1)" }) { double1 = alpha.toDouble() } + } + super.setAlpha(alpha) + } + + private val isDrawn: Boolean + get() = (mPrivateFlags and 0x20 /* PFLAG_DRAWN */) > 0 + override fun invalidate() { - logger.d("invalidate()") + if (isDrawn && visibility == VISIBLE) { + logger.d("invalidate()") + } + super.invalidate() (parent as? FlexClockView)?.invalidate() } @@ -490,22 +529,22 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) } val AOD_COLOR = Color.WHITE - val OPTICAL_SIZE_AXIS = ClockFontAxisSetting("opsz", 144f) + val OPTICAL_SIZE_AXIS = ClockFontAxisSetting(GSFAxes.OPTICAL_SIZE, 144f) val DEFAULT_LS_VARIATION = listOf( OPTICAL_SIZE_AXIS, - ClockFontAxisSetting("wght", 400f), - ClockFontAxisSetting("wdth", 100f), - ClockFontAxisSetting("ROND", 0f), - ClockFontAxisSetting("slnt", 0f), + ClockFontAxisSetting(GSFAxes.WEIGHT, 400f), + ClockFontAxisSetting(GSFAxes.WIDTH, 100f), + ClockFontAxisSetting(GSFAxes.ROUND, 0f), + ClockFontAxisSetting(GSFAxes.SLANT, 0f), ) val DEFAULT_AOD_VARIATION = listOf( OPTICAL_SIZE_AXIS, - ClockFontAxisSetting("wght", 200f), - ClockFontAxisSetting("wdth", 100f), - ClockFontAxisSetting("ROND", 0f), - ClockFontAxisSetting("slnt", 0f), + ClockFontAxisSetting(GSFAxes.WEIGHT, 200f), + ClockFontAxisSetting(GSFAxes.WIDTH, 100f), + ClockFontAxisSetting(GSFAxes.ROUND, 0f), + ClockFontAxisSetting(GSFAxes.SLANT, 0f), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt index b0f81c012cca..f44769d522eb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt @@ -7,11 +7,6 @@ import junit.framework.Assert import org.junit.Test import org.junit.runner.RunWith -private const val TAG_WGHT = "wght" -private const val TAG_WDTH = "wdth" -private const val TAG_OPSZ = "opsz" -private const val TAG_ROND = "ROND" - @RunWith(AndroidJUnit4::class) @SmallTest class FontVariationUtilsTest : SysuiTestCase() { @@ -23,19 +18,22 @@ class FontVariationUtilsTest : SysuiTestCase() { weight = 100, width = 100, opticalSize = -1, - roundness = 100 + roundness = 100, ) - Assert.assertEquals("'$TAG_WGHT' 100, '$TAG_WDTH' 100, '$TAG_ROND' 100", initFvar) + Assert.assertEquals( + "'${GSFAxes.WEIGHT}' 100, '${GSFAxes.WIDTH}' 100, '${GSFAxes.ROUND}' 100", + initFvar, + ) val updatedFvar = fontVariationUtils.updateFontVariation( weight = 200, width = 100, opticalSize = 0, - roundness = 100 + roundness = 100, ) Assert.assertEquals( - "'$TAG_WGHT' 200, '$TAG_WDTH' 100, '$TAG_OPSZ' 0, '$TAG_ROND' 100", - updatedFvar + "'${GSFAxes.WEIGHT}' 200, '${GSFAxes.WIDTH}' 100, '${GSFAxes.OPTICAL_SIZE}' 0, '${GSFAxes.ROUND}' 100", + updatedFvar, ) } @@ -46,14 +44,14 @@ class FontVariationUtilsTest : SysuiTestCase() { weight = 100, width = 100, opticalSize = 0, - roundness = 100 + roundness = 100, ) val updatedFvar1 = fontVariationUtils.updateFontVariation( weight = 100, width = 100, opticalSize = 0, - roundness = 100 + roundness = 100, ) Assert.assertEquals("", updatedFvar1) val updatedFvar2 = fontVariationUtils.updateFontVariation() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java index 83b68fed768e..647603c418ee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java @@ -17,8 +17,8 @@ package com.android.systemui.navigationbar.views; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; import static android.inputmethodservice.InputMethodService.IME_VISIBLE; @@ -31,8 +31,9 @@ import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_HOME_BUT import static com.android.systemui.navigationbar.views.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS; import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS; import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_ALT_BACK; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.google.common.truth.Truth.assertThat; @@ -500,8 +501,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(true)); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_ALT_BACK), eq(true)); } /** @@ -514,8 +516,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(true)); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_ALT_BACK), eq(true)); } /** @@ -531,8 +534,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */, BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(false)); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_ALT_BACK), eq(false)); } /** @@ -545,8 +549,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_ADJUST_NOTHING, true /* showImeSwitcher */); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(true)); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_ALT_BACK), eq(false)); } @Test @@ -567,26 +572,26 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, true); // Verify IME window state will be updated in default NavBar & external NavBar state reset. - assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN - | NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN, + assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_VISIBLE + | NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE, defaultNavBar.getNavigationIconHints()); assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); assertFalse((externalNavBar.getNavigationIconHints() - & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */, BACK_DISPOSITION_DEFAULT, false); // Verify IME window state will be updated in external NavBar & default NavBar state reset. - assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN - | NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN, + assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_VISIBLE + | NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE, externalNavBar.getNavigationIconHints()); assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); assertFalse((defaultNavBar.getNavigationIconHints() - & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); } @Test @@ -603,9 +608,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); assertTrue((mNavigationBar.getNavigationIconHints() - & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); // Verify navbar didn't alter and showing back icon when the keyguard is showing without // requesting IME insets visible. @@ -613,9 +618,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); assertFalse((mNavigationBar.getNavigationIconHints() - & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); // Verify navbar altered and showing back icon when the keyguard is showing and // requesting IME insets visible. @@ -624,9 +629,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); assertTrue((mNavigationBar.getNavigationIconHints() - & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt index f005375a2ef9..7bb28dbabd5e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt @@ -49,6 +49,7 @@ import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger +import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.FakeSettings @@ -146,6 +147,7 @@ class ModesTileTest : SysuiTestCase() { tileDataInteractor, mapper, userActionInteractor, + kosmos.modesDialogViewModel, ) underTest.initialize() diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml index 1d5e09d9b260..e1e60920ab01 100644 --- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml +++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml @@ -26,6 +26,9 @@ <padding android:left="20dp" android:right="20dp" /> + <!-- TODO(b/294830092): Update to the blur surface effect color token when + the resource workaround is resolved and + notification_shade_blur is enabled. --> <solid android:color="@androidprv:color/materialColorSurfaceContainerHigh" /> </shape> </inset> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 449e4bb46027..724a12c12490 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -267,7 +267,7 @@ <dimen name="notification_max_height">358dp</dimen> <!-- Height of a large promoted ongoing notification in the status bar --> - <dimen name="notification_max_height_for_promoted_ongoing">218dp</dimen> + <dimen name="notification_max_height_for_promoted_ongoing">272dp</dimen> <!-- Height of a heads up notification in the status bar for legacy custom views --> <dimen name="notification_max_heads_up_height_legacy">128dp</dimen> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index d363e524a9f2..011458859db4 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -23,7 +23,7 @@ import android.os.IRemoteCallback; import android.view.MotionEvent; import com.android.systemui.shared.recents.ISystemUiProxy; -// Next ID: 38 +// Next ID: 39 oneway interface IOverviewProxy { void onActiveNavBarRegionChanges(in Region activeRegion) = 11; @@ -154,4 +154,9 @@ oneway interface IOverviewProxy { * Sent when {@link TaskbarDelegate#onDisplayRemoved} is called. */ void onDisplayRemoved(int displayId) = 37; + + /** + * Sent when {@link TaskbarDelegate#onDisplayRemoveSystemDecorations} is called. + */ + void onDisplayRemoveSystemDecorations(int displayId) = 38; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 8576a6ebac42..a518c57bdd16 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -72,6 +72,25 @@ public class Task { @ViewDebug.ExportedProperty(category = "recents") public final int displayId; + /** + * The component of the first activity in the task, can be considered the "application" of + * this task. + */ + @Nullable + public ComponentName baseActivity; + /** + * The number of activities in this task (including running). + */ + public int numActivities; + /** + * Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}. + */ + public boolean isTopActivityNoDisplay; + /** + * Whether fillsParent() is false for every activity in the tasks stack. + */ + public boolean isActivityStackTransparent; + // The source component name which started this task public final ComponentName sourceComponent; @@ -90,6 +109,10 @@ public class Task { this.userId = t.userId; this.lastActiveTime = t.lastActiveTime; this.displayId = t.displayId; + this.baseActivity = t.baseActivity; + this.numActivities = t.numActivities; + this.isTopActivityNoDisplay = t.isTopActivityNoDisplay; + this.isActivityStackTransparent = t.isActivityStackTransparent; updateHashCode(); } @@ -106,7 +129,9 @@ public class Task { } public TaskKey(int id, int windowingMode, @NonNull Intent intent, - ComponentName sourceComponent, int userId, long lastActiveTime, int displayId) { + ComponentName sourceComponent, int userId, long lastActiveTime, int displayId, + @Nullable ComponentName baseActivity, int numActivities, + boolean isTopActivityNoDisplay, boolean isActivityStackTransparent) { this.id = id; this.windowingMode = windowingMode; this.baseIntent = intent; @@ -114,6 +139,10 @@ public class Task { this.userId = userId; this.lastActiveTime = lastActiveTime; this.displayId = displayId; + this.baseActivity = baseActivity; + this.numActivities = numActivities; + this.isTopActivityNoDisplay = isTopActivityNoDisplay; + this.isActivityStackTransparent = isActivityStackTransparent; updateHashCode(); } @@ -185,6 +214,10 @@ public class Task { parcel.writeLong(lastActiveTime); parcel.writeInt(displayId); parcel.writeTypedObject(sourceComponent, flags); + parcel.writeTypedObject(baseActivity, flags); + parcel.writeInt(numActivities); + parcel.writeBoolean(isTopActivityNoDisplay); + parcel.writeBoolean(isActivityStackTransparent); } private static TaskKey readFromParcel(Parcel parcel) { @@ -195,9 +228,14 @@ public class Task { long lastActiveTime = parcel.readLong(); int displayId = parcel.readInt(); ComponentName sourceComponent = parcel.readTypedObject(ComponentName.CREATOR); + ComponentName baseActivity = parcel.readTypedObject(ComponentName.CREATOR); + int numActivities = parcel.readInt(); + boolean isTopActivityNoDisplay = parcel.readBoolean(); + boolean isActivityStackTransparent = parcel.readBoolean(); return new TaskKey(id, windowingMode, baseIntent, sourceComponent, userId, - lastActiveTime, displayId); + lastActiveTime, displayId, baseActivity, numActivities, isTopActivityNoDisplay, + isActivityStackTransparent); } @Override diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 818e39800b0c..32a76fb8439a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -17,8 +17,8 @@ package com.android.systemui.shared.recents.utilities; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import android.annotation.TargetApi; import android.app.StatusBarManager.NavigationHint; @@ -107,19 +107,22 @@ public class Utilities { * Gets the updated navigation icon hints, based on the current ones and the given IME state. * * @param oldHints current navigation icon hints. - * @param backDisposition the IME back disposition mode. - * @param imeShown whether the IME is currently visible. - * @param showImeSwitcher whether the IME Switcher button should be shown. + * @param backDisposition the IME back disposition mode. Only takes effect if + * {@code isImeVisible} is {@code true}. + * @param isImeVisible whether the IME is currently visible. + * @param showImeSwitcher whether the IME Switcher button should be shown. Only takes effect if + * {@code isImeVisible} is {@code true}. */ @NavigationHint public static int calculateNavigationIconHints(@NavigationHint int oldHints, - @BackDispositionMode int backDisposition, boolean imeShown, boolean showImeSwitcher) { + @BackDispositionMode int backDisposition, boolean isImeVisible, + boolean showImeSwitcher) { int hints = oldHints; switch (backDisposition) { case InputMethodService.BACK_DISPOSITION_DEFAULT: case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS: case InputMethodService.BACK_DISPOSITION_WILL_DISMISS: - if (imeShown) { + if (isImeVisible) { hints |= NAVIGATION_HINT_BACK_ALT; } else { hints &= ~NAVIGATION_HINT_BACK_ALT; @@ -129,15 +132,15 @@ public class Utilities { hints &= ~NAVIGATION_HINT_BACK_ALT; break; } - if (imeShown) { - hints |= NAVIGATION_HINT_IME_SHOWN; + if (isImeVisible) { + hints |= NAVIGATION_HINT_IME_VISIBLE; } else { - hints &= ~NAVIGATION_HINT_IME_SHOWN; + hints &= ~NAVIGATION_HINT_IME_VISIBLE; } - if (showImeSwitcher) { - hints |= NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; + if (showImeSwitcher && isImeVisible) { + hints |= NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; } else { - hints &= ~NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; + hints &= ~NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; } return hints; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index f87d9b05d795..0048fd4d33d3 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -99,11 +99,11 @@ public class QuickStepContract { // Allow system gesture no matter the system bar(s) is visible or not public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17; // The IME is visible. - public static final long SYSUI_STATE_IME_SHOWING = 1L << 18; + public static final long SYSUI_STATE_IME_VISIBLE = 1L << 18; // The window magnification is overlapped with system gesture insets at the bottom. public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19; // The IME Switcher button is visible. - public static final long SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING = 1L << 20; + public static final long SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE = 1L << 20; // Device dozing/AOD state public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21; // The home feature is disabled (either by SUW/SysUI/device policy) @@ -134,6 +134,8 @@ public class QuickStepContract { public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34; // Communal hub is showing public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35; + // The back button is adjusted for the IME. + public static final long SYSUI_STATE_IME_ALT_BACK = 1L << 36; // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants @@ -168,9 +170,9 @@ public class QuickStepContract { SYSUI_STATE_DIALOG_SHOWING, SYSUI_STATE_ONE_HANDED_ACTIVE, SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, - SYSUI_STATE_IME_SHOWING, + SYSUI_STATE_IME_VISIBLE, SYSUI_STATE_MAGNIFICATION_OVERLAP, - SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING, + SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE, SYSUI_STATE_DEVICE_DOZING, SYSUI_STATE_BACK_DISABLED, SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, @@ -185,6 +187,7 @@ public class QuickStepContract { SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED, SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, SYSUI_STATE_COMMUNAL_HUB_SHOWING, + SYSUI_STATE_IME_ALT_BACK, }) public @interface SystemUiStateFlags {} @@ -244,13 +247,13 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { str.add("allow_gesture"); } - if ((flags & SYSUI_STATE_IME_SHOWING) != 0) { + if ((flags & SYSUI_STATE_IME_VISIBLE) != 0) { str.add("ime_visible"); } if ((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0) { str.add("magnification_overlap"); } - if ((flags & SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING) != 0) { + if ((flags & SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE) != 0) { str.add("ime_switcher_button_visible"); } if ((flags & SYSUI_STATE_DEVICE_DOZING) != 0) { @@ -295,6 +298,9 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) { str.add("communal_hub_showing"); } + if ((flags & SYSUI_STATE_IME_ALT_BACK) != 0) { + str.add("ime_alt_back"); + } return str.toString(); } diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java index ae282c7e14bd..e2ac6a494020 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java @@ -22,6 +22,7 @@ import android.text.TextUtils; import android.text.method.SingleLineTransformationMethod; import android.util.AttributeSet; import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.TextView; import com.android.systemui.res.R; @@ -65,6 +66,14 @@ public class CarrierText extends TextView { } } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + // Clear selected state set by CarrierTextController so "selected" not announced by + // accessibility but we can still marquee. + info.setSelected(false); + } + public boolean getShowAirplaneMode() { return mShowAirplaneMode; } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt index d785b5b5a7e7..464201f6ec12 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt @@ -29,12 +29,12 @@ import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET import android.view.KeyEvent.META_CTRL_ON import android.view.KeyEvent.META_META_ON import android.view.KeyboardShortcutGroup +import android.window.DesktopModeFlags import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo import com.android.systemui.res.R import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut -import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import javax.inject.Inject @@ -85,7 +85,8 @@ constructor(@Main private val resources: Resources, @Application private val con ) } if ( - DesktopModeStatus.canEnterDesktopMode(context) && enableTaskResizingKeyboardShortcuts() + DesktopModeStatus.canEnterDesktopMode(context) && + DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue ) { // Snap a freeform window to the left // - Meta + Left bracket diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt index 550438aa220f..9d43c48ee274 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt @@ -59,7 +59,12 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.LiveRegionMode +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.liveRegion +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.android.compose.ui.graphics.painter.rememberDrawablePainter @@ -244,7 +249,10 @@ private fun ErrorMessageContainer(errorMessage: String) { lineHeight = 20.sp, fontWeight = FontWeight.W500, color = MaterialTheme.colorScheme.error, - modifier = Modifier.padding(start = 24.dp).width(252.dp), + modifier = Modifier.padding(start = 24.dp).width(252.dp).semantics { + contentDescription = errorMessage + liveRegion = LiveRegionMode.Polite + }, ) } } @@ -397,6 +405,7 @@ private fun Description(text: String) { .width(316.dp) .wrapContentSize(Alignment.Center), color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center ) } diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt index 425e674ec804..fb69b793d975 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt @@ -21,6 +21,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize import com.android.systemui.log.LogcatEchoTracker import com.android.systemui.util.time.SystemClock +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject @SysUISingleton @@ -31,7 +32,7 @@ constructor( private val systemClock: SystemClock, private val logcatEchoTracker: LogcatEchoTracker, ) { - private val existingBuffers = mutableMapOf<String, TableLogBuffer>() + private val existingBuffers = ConcurrentHashMap<String, TableLogBuffer>() /** * Creates a new [TableLogBuffer]. This method should only be called from static contexts, where @@ -42,17 +43,9 @@ constructor( * @param maxSize the buffer max size. See [adjustMaxSize] * @return a new [TableLogBuffer] registered with [DumpManager] */ - fun create( - name: String, - maxSize: Int, - ): TableLogBuffer { + fun create(name: String, maxSize: Int): TableLogBuffer { val tableBuffer = - TableLogBuffer( - adjustMaxSize(maxSize), - name, - systemClock, - logcatEchoTracker, - ) + TableLogBuffer(adjustMaxSize(maxSize), name, systemClock, logcatEchoTracker) dumpManager.registerTableLogBuffer(name, tableBuffer) return tableBuffer } @@ -66,13 +59,12 @@ constructor( * * @return a [TableLogBuffer] suitable for reuse */ - fun getOrCreate( - name: String, - maxSize: Int, - ): TableLogBuffer = - existingBuffers.getOrElse(name) { - val buffer = create(name, maxSize) - existingBuffers[name] = buffer - buffer + fun getOrCreate(name: String, maxSize: Int): TableLogBuffer = + synchronized(existingBuffers) { + existingBuffers.getOrElse(name) { + val buffer = create(name, maxSize) + existingBuffers[name] = buffer + buffer + } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 40197b2daf49..0de8c40bddaa 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -536,12 +536,12 @@ public final class NavBarHelper implements } /** - * Checks whether the IME is shown on top of the screen, based on the given IME window + * Checks whether the IME is visible on top of the screen, based on the given IME window * visibility flags, and the current keyguard state. * * @param vis the IME window visibility. */ - public boolean isImeShown(@ImeWindowVisibility int vis) { + public boolean isImeVisible(@ImeWindowVisibility int vis) { View shadeWindowView = mNotificationShadeWindowController.getWindowRootView(); boolean isKeyguardShowing = mKeyguardStateController.isShowing(); boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow() diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index ee24b3120966..c2dacd6690a0 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -17,8 +17,9 @@ package com.android.systemui.navigationbar; import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -30,8 +31,9 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_ALT_BACK; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; @@ -263,6 +265,20 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } } + @Override + public void onDisplayRemoveSystemDecorations(int displayId) { + CommandQueue.Callbacks.super.onDisplayRemoveSystemDecorations(displayId); + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onDisplayRemoveSystemDecorations(displayId); + } catch (RemoteException e) { + Log.e(TAG, "onDisplaySystemDecorationsRemoved() failed", e); + } + } + // Separated into a method to keep setDependencies() clean/readable. private LightBarTransitionsController createLightBarTransitionsController() { @@ -362,10 +378,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mSysUiState.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable) .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable) - .setFlag(SYSUI_STATE_IME_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0) - .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0) + .setFlag(SYSUI_STATE_IME_VISIBLE, + (mNavigationIconHints & NAVIGATION_HINT_IME_VISIBLE) != 0) + .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE, + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0) + .setFlag(SYSUI_STATE_IME_ALT_BACK, + (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0) .setFlag(SYSUI_STATE_OVERVIEW_DISABLED, (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0) .setFlag(SYSUI_STATE_HOME_DISABLED, @@ -485,14 +503,11 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, @BackDispositionMode int backDisposition, boolean showImeSwitcher) { - boolean imeShown = mNavBarHelper.isImeShown(vis); - if (!imeShown) { - // Count imperceptible changes as visible so we transition taskbar out quickly. - imeShown = (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0; - } - showImeSwitcher = imeShown && showImeSwitcher; - int hints = Utilities.calculateNavigationIconHints(mNavigationIconHints, backDisposition, - imeShown, showImeSwitcher); + // Count imperceptible changes as visible so we transition taskbar out quickly. + final boolean isImeVisible = mNavBarHelper.isImeVisible(vis) + || (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0; + final int hints = Utilities.calculateNavigationIconHints(mNavigationIconHints, + backDisposition, isImeVisible, showImeSwitcher); if (hints == mNavigationIconHints) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java index 6842dbc2b12d..2744f9f84ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java @@ -17,8 +17,9 @@ package com.android.systemui.navigationbar.views; import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; @@ -43,8 +44,9 @@ import static com.android.systemui.shared.statusbar.phone.BarTransitions.Transit import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_ALT_BACK; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode; @@ -652,18 +654,18 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (!mEdgeBackGestureHandler.isHandlingGestures()) { // We're in 2/3 button mode OR back button force-shown in SUW if (!mImeVisible) { - // IME not showing, take all touches + // IME is not visible, take all touches info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); return; } if (!mView.isImeRenderingNavButtons()) { - // IME showing but not drawing any buttons, take all touches + // IME is visible but not drawing any buttons, take all touches info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); return; } } - // When in gestural and the IME is showing, don't use the nearest region since it will + // When in gestural and the IME is visible, don't use the nearest region since it will // take gesture space away from the IME info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); info.touchableRegion.set( @@ -1138,10 +1140,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (displayId != mDisplayId) { return; } - boolean imeShown = mNavBarHelper.isImeShown(vis); - showImeSwitcher = imeShown && showImeSwitcher; - int hints = Utilities.calculateNavigationIconHints(mNavigationIconHints, backDisposition, - imeShown, showImeSwitcher); + final boolean isImeVisible = mNavBarHelper.isImeVisible(vis); + final int hints = Utilities.calculateNavigationIconHints(mNavigationIconHints, + backDisposition, isImeVisible, showImeSwitcher); if (hints == mNavigationIconHints) { return; } @@ -1685,10 +1686,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mSysUiFlagsContainer.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable) .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable) .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isNavBarWindowVisible()) - .setFlag(SYSUI_STATE_IME_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0) - .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0) + .setFlag(SYSUI_STATE_IME_VISIBLE, + (mNavigationIconHints & NAVIGATION_HINT_IME_VISIBLE) != 0) + .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE, + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0) + .setFlag(SYSUI_STATE_IME_ALT_BACK, + (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0) .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, allowSystemGestureIgnoringBarVisibility()) .commitUpdate(mDisplayId); @@ -1953,7 +1956,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (newBackAlt != oldBackAlt) { mView.onBackAltChanged(newBackAlt); } - mImeVisible = (hints & NAVIGATION_HINT_IME_SHOWN) != 0; + mImeVisible = (hints & NAVIGATION_HINT_IME_VISIBLE) != 0; mView.setNavigationIconHints(hints); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java index ed8e538cc895..de61d404b1e8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java @@ -17,8 +17,8 @@ package com.android.systemui.navigationbar.views; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; @@ -612,9 +612,9 @@ public class NavigationBarView extends FrameLayout { updateRecentsIcon(); // Update IME switcher button visibility, a11y and rotate button always overrides - // the appearance - boolean isImeSwitcherButtonVisible = - (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0 + // the appearance. + final boolean isImeSwitcherButtonVisible = + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0 && !isImeRenderingNavButtons(); mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, isImeSwitcherButtonVisible); @@ -670,7 +670,7 @@ public class NavigationBarView extends FrameLayout { boolean isImeRenderingNavButtons() { return mImeDrawsImeNavBar && mImeCanRenderGesturalNavButtons - && (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0; + && (mNavigationIconHints & NAVIGATION_HINT_IME_VISIBLE) != 0; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt index 0051bf5de7f2..ad5dd27f07c2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt @@ -35,12 +35,14 @@ import com.android.systemui.modes.shared.ModesUiIcons import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.asQSTileIcon import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.dialog.ModesDetailsViewModel import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel @@ -48,6 +50,7 @@ import com.android.systemui.qs.tiles.impl.modes.ui.ModesTileMapper import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel import javax.inject.Inject import kotlinx.coroutines.runBlocking @@ -67,6 +70,7 @@ constructor( private val dataInteractor: ModesTileDataInteractor, private val tileMapper: ModesTileMapper, private val userActionInteractor: ModesTileUserActionInteractor, + private val modesDialogViewModel: ModesDialogViewModel, ) : QSTileImpl<QSTile.State>( host, @@ -114,6 +118,13 @@ constructor( userActionInteractor.handleToggleClick(model) } + override fun getDetailsViewModel(): TileDetailsViewModel { + return ModesDetailsViewModel( + onSettingsClick = { userActionInteractor.handleLongClick(null) }, + viewModel = modesDialogViewModel, + ) + } + override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt new file mode 100644 index 000000000000..511597d05d37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 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.qs.tiles.dialog + +import androidx.compose.runtime.Composable +import com.android.systemui.plugins.qs.TileDetailsViewModel +import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid +import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel + +/** The view model used for the modes details view in the Quick Settings */ +class ModesDetailsViewModel( + private val onSettingsClick: () -> Unit, + private val viewModel: ModesDialogViewModel, +) : TileDetailsViewModel() { + @Composable + override fun GetContentView() { + // TODO(b/378513940): Finish implementing this function. + ModeTileGrid(viewModel = viewModel) + } + + override fun clickOnSettingsButton() { + onSettingsClick() + } + + override fun getTitle(): String { + // TODO(b/388321032): Replace this string with a string in a translatable xml file. + return "Modes" + } + + override fun getSubTitle(): String { + // TODO(b/388321032): Replace this string with a string in a translatable xml file. + return "Silences interruptions from people and apps in different circumstances" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt index 5ce7f0d039c8..b5da044b886a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt @@ -54,7 +54,7 @@ constructor( handleToggleClick(input.data) } is QSTileUserAction.LongClick -> { - qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent) + handleLongClick(action.expandable) } } } @@ -95,6 +95,10 @@ constructor( } } + fun handleLongClick(expandable: Expandable?) { + qsTileIntentUserInputHandler.handle(expandable, longClickIntent) + } + companion object { const val TAG = "ModesTileUserActionInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index d401283aa84e..96192b1ea315 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.footer.ui.view; import static android.graphics.PorterDuff.Mode.SRC_ATOP; import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization; +import static com.android.systemui.Flags.notificationShadeBlur; import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.annotation.ColorInt; @@ -29,6 +30,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; @@ -39,6 +41,8 @@ import android.widget.TextView; import androidx.annotation.NonNull; +import com.android.internal.graphics.ColorUtils; +import com.android.systemui.common.shared.colors.SurfaceEffectColors; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.notification.ColorUpdateLogger; @@ -383,9 +387,23 @@ public class FooterView extends StackScrollerDecorView { final Drawable historyBg = NotifRedesignFooter.isEnabled() ? theme.getDrawable(R.drawable.notif_footer_btn_background) : null; final @ColorInt int scHigh; + if (!notificationFooterBackgroundTintOptimization()) { - scHigh = mContext.getColor( - com.android.internal.R.color.materialColorSurfaceContainerHigh); + if (notificationShadeBlur()) { + Color backgroundColor = Color.valueOf( + SurfaceEffectColors.surfaceEffect0(getResources())); + scHigh = ColorUtils.setAlphaComponent(backgroundColor.toArgb(), 0xFF); + // Apply alpha on background drawables. + int backgroundAlpha = (int) (backgroundColor.alpha() * 0xFF); + clearAllBg.setAlpha(backgroundAlpha); + settingsBg.setAlpha(backgroundAlpha); + if (historyBg != null) { + historyBg.setAlpha(backgroundAlpha); + } + } else { + scHigh = mContext.getColor( + com.android.internal.R.color.materialColorSurfaceContainerHigh); + } if (scHigh != 0) { final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP); clearAllBg.setColorFilter(bgColorFilter); diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index 69b7e892a380..9795cda97f37 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -49,8 +49,10 @@ import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton +import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.gesture.isFourFingerTouchpadSwipe import com.android.systemui.touchpad.tutorial.ui.gesture.isThreeFingerTouchpadSwipe @@ -80,6 +82,7 @@ fun TutorialSelectionScreen( } ), ) { + val padding = if (hasCompactWindowSize()) 24.dp else 60.dp val configuration = LocalConfiguration.current when (configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { @@ -88,7 +91,7 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(60.dp), + modifier = Modifier.weight(1f).padding(padding), lastSelectedScreen, ) } @@ -98,7 +101,7 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(60.dp), + modifier = Modifier.weight(1f).padding(padding), lastSelectedScreen, ) } @@ -106,7 +109,7 @@ fun TutorialSelectionScreen( // because other composables have weight 1, Done button will be positioned first DoneButton( onDoneButtonClicked = onDoneButtonClicked, - modifier = Modifier.padding(horizontal = 60.dp), + modifier = Modifier.padding(horizontal = padding), ) } } @@ -146,7 +149,7 @@ private fun VerticalSelectionButtons( lastSelectedScreen: Screen, ) { Column( - verticalArrangement = Arrangement.spacedBy(20.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, ) { @@ -244,8 +247,13 @@ private fun TutorialButton( modifier = Modifier.width(30.dp).height(30.dp), tint = iconColor, ) - Spacer(modifier = Modifier.height(16.dp)) - Text(text = text, style = MaterialTheme.typography.headlineLarge, color = iconColor) + if (!hasCompactWindowSize()) Spacer(modifier = Modifier.height(16.dp)) + Text( + text = text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.headlineLarge, + color = iconColor, + ) } } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 75c629b77700..fda57d6bb986 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -45,6 +45,7 @@ import android.view.MotionEvent.PointerProperties; import android.view.accessibility.AccessibilityEvent; import com.android.server.LocalServices; +import com.android.server.accessibility.autoclick.AutoclickController; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index 3415300ec8d1..1bc9c783df76 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.android.server.accessibility; +package com.android.server.accessibility.autoclick; import static android.view.MotionEvent.BUTTON_PRIMARY; import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT; import static android.view.accessibility.AccessibilityManager.AUTOCLICK_DELAY_DEFAULT; import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT; -import static com.android.server.accessibility.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME; +import static com.android.server.accessibility.autoclick.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME; import android.accessibilityservice.AccessibilityTrace; import android.annotation.NonNull; @@ -43,6 +43,9 @@ import android.view.WindowManager; import androidx.annotation.VisibleForTesting; import com.android.internal.accessibility.util.AccessibilityUtils; +import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.BaseEventStreamTransformation; +import com.android.server.accessibility.Flags; /** * Implements "Automatically click on mouse stop" feature. diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java index 93ce1f044f0b..557e1b2afcd5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.accessibility; +package com.android.server.accessibility.autoclick; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT; diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java index c35f4fca6edd..fece7a899f0a 100644 --- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java +++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java @@ -69,6 +69,8 @@ class AudioManagerShellCommand extends ShellCommand { return getMinVolume(); case "get-max-volume": return getMaxVolume(); + case "get-stream-volume": + return getStreamVolume(); case "set-device-volume": return setDeviceVolume(); case "adj-mute": @@ -114,6 +116,8 @@ class AudioManagerShellCommand extends ShellCommand { pw.println(" Gets the min volume for STREAM_TYPE"); pw.println(" get-max-volume STREAM_TYPE"); pw.println(" Gets the max volume for STREAM_TYPE"); + pw.println(" get-stream-volume STREAM_TYPE"); + pw.println(" Gets the volume for STREAM_TYPE"); pw.println(" set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE"); pw.println(" Sets for NATIVE_DEVICE_TYPE the STREAM_TYPE volume to VOLUME_INDEX"); pw.println(" adj-mute STREAM_TYPE"); @@ -322,6 +326,15 @@ class AudioManagerShellCommand extends ShellCommand { return 0; } + private int getStreamVolume() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + final int stream = readIntArg(); + final int result = am.getStreamVolume(stream); + getOutPrintWriter().println("AudioManager.getStreamVolume(" + stream + ") -> " + result); + return 0; + } + private int setDeviceVolume() { final Context context = mService.mContext; final AudioDeviceVolumeManager advm = (AudioDeviceVolumeManager) context.getSystemService( diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 336243f0289e..320dd8f188c0 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -50,6 +50,7 @@ import static android.media.AudioManager.STREAM_SYSTEM; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; import static android.media.audio.Flags.cacheGetStreamMinMaxVolume; +import static android.media.audio.Flags.cacheGetStreamVolume; import static android.media.audio.Flags.concurrentAudioRecordBypassPermission; import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency; import static android.media.audio.Flags.focusFreezeTestApi; @@ -1910,6 +1911,12 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.onRoutingUpdated(); } checkMuteAwaitConnection(); + if (cacheGetStreamVolume()) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear volume cache after routing update"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } } //----------------------------------------------------------------- @@ -4988,6 +4995,8 @@ public class AudioService extends IAudioService.Stub + concurrentAudioRecordBypassPermission()); pw.println("\tandroid.media.audio.Flags.cacheGetStreamMinMaxVolume:" + cacheGetStreamMinMaxVolume()); + pw.println("\tandroid.media.audio.Flags.cacheGetStreamVolume:" + + cacheGetStreamVolume()); } private void dumpAudioMode(PrintWriter pw) { @@ -7054,6 +7063,13 @@ public class AudioService extends IAudioService.Stub streamState.mIsMuted = false; } } + if (cacheGetStreamVolume()) { + if (DEBUG_VOL) { + Log.d(TAG, + "Clear volume cache after possibly changing mute in readAudioSettings"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } } readVolumeGroupsSettings(userSwitch); @@ -9274,11 +9290,23 @@ public class AudioService extends IAudioService.Stub public void put(int key, int value) { super.put(key, value); record("put", key, value); + if (cacheGetStreamVolume()) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear volume cache after update index map"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } } @Override public void setValueAt(int index, int value) { super.setValueAt(index, value); record("setValueAt", keyAt(index), value); + if (cacheGetStreamVolume()) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear volume cache after update index map"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } } // Record all changes in the VolumeStreamState @@ -10008,8 +10036,9 @@ public class AudioService extends IAudioService.Stub * @return true if the mute state was changed */ public boolean mute(boolean state, boolean apply, String src) { + boolean changed; synchronized (VolumeStreamState.class) { - boolean changed = state != mIsMuted; + changed = state != mIsMuted; if (changed) { sMuteLogger.enqueue( new AudioServiceEvents.StreamMuteEvent(mStreamType, state, src)); @@ -10027,8 +10056,16 @@ public class AudioService extends IAudioService.Stub doMute(); } } - return changed; } + + if (cacheGetStreamVolume() && changed) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear volume cache after changing mute state"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } + + return changed; } public void doMute() { diff --git a/services/core/java/com/android/server/display/plugin/PluginManager.java b/services/core/java/com/android/server/display/plugin/PluginManager.java index d4099975cafa..cb0a4574361a 100644 --- a/services/core/java/com/android/server/display/plugin/PluginManager.java +++ b/services/core/java/com/android/server/display/plugin/PluginManager.java @@ -74,15 +74,17 @@ public class PluginManager { /** * Adds change listener for particular plugin type */ - public <T> void subscribe(PluginType<T> type, PluginChangeListener<T> listener) { - mPluginStorage.addListener(type, listener); + public <T> void subscribe(PluginType<T> type, String uniqueDisplayId, + PluginChangeListener<T> listener) { + mPluginStorage.addListener(type, uniqueDisplayId, listener); } /** * Removes change listener */ - public <T> void unsubscribe(PluginType<T> type, PluginChangeListener<T> listener) { - mPluginStorage.removeListener(type, listener); + public <T> void unsubscribe(PluginType<T> type, String uniqueDisplayId, + PluginChangeListener<T> listener) { + mPluginStorage.removeListener(type, uniqueDisplayId, listener); } /** diff --git a/services/core/java/com/android/server/display/plugin/PluginStorage.java b/services/core/java/com/android/server/display/plugin/PluginStorage.java index dd3415fb614d..5102c2709329 100644 --- a/services/core/java/com/android/server/display/plugin/PluginStorage.java +++ b/services/core/java/com/android/server/display/plugin/PluginStorage.java @@ -20,10 +20,13 @@ import android.annotation.Nullable; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.tools.r8.keepanno.annotations.KeepForApi; import java.io.PrintWriter; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -35,42 +38,97 @@ import java.util.Set; public class PluginStorage { private static final String TAG = "PluginStorage"; + // Special ID used to indicate that given value is to be applied globally, rather than to a + // specific display. If both GLOBAL and specific display values are present - specific display + // value is selected. + @VisibleForTesting + static final String GLOBAL_ID = "GLOBAL"; + private final Object mLock = new Object(); @GuardedBy("mLock") - private final Map<PluginType<?>, Object> mValues = new HashMap<>(); + private final Map<PluginType<?>, ValuesContainer<?>> mValues = new HashMap<>(); @GuardedBy("mLock") private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>(); @GuardedBy("mLock") - private final PluginEventStorage mPluginEventStorage = new PluginEventStorage(); + private final Map<String, PluginEventStorage> mPluginEventStorages = new HashMap<>(); + + /** + * Updates value in storage and forwards it to corresponding listeners for all displays + * that does not have display specific value. + * Should be called by OEM Plugin implementation in order to communicate with Framework + */ + @KeepForApi + public <T> void updateGlobalValue(PluginType<T> type, @Nullable T value) { + updateValue(type, GLOBAL_ID, value); + } /** - * Updates value in storage and forwards it to corresponding listeners. - * Should be called by OEM Plugin implementation in order to provide communicate with Framework + * Updates value in storage and forwards it to corresponding listeners for specific display. + * Should be called by OEM Plugin implementation in order to communicate with Framework + * @param type - plugin type, that need to be updated + * @param uniqueDisplayId - uniqueDisplayId that this type/value should be applied to + * @param value - plugin value for particular type and display */ @KeepForApi - public <T> void updateValue(PluginType<T> type, @Nullable T value) { - Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value); + public <T> void updateValue(PluginType<T> type, String uniqueDisplayId, @Nullable T value) { + Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value + + "; displayId=" + uniqueDisplayId); Set<PluginManager.PluginChangeListener<T>> localListeners; + T valueToNotify; synchronized (mLock) { - mValues.put(type, value); - mPluginEventStorage.onValueUpdated(type); - ListenersContainer<T> container = getListenersContainerForTypeLocked(type); - localListeners = new LinkedHashSet<>(container.mListeners); + ValuesContainer<T> valuesByType = getValuesContainerLocked(type); + valuesByType.updateValueLocked(uniqueDisplayId, value); + // if value was set to null, we might need to notify with GLOBAL value instead + valueToNotify = valuesByType.getValueLocked(uniqueDisplayId); + + PluginEventStorage storage = mPluginEventStorages.computeIfAbsent(uniqueDisplayId, + d -> new PluginEventStorage()); + storage.onValueUpdated(type); + + localListeners = getListenersForUpdateLocked(type, uniqueDisplayId); } Slog.d(TAG, "updateValue, notifying listeners=" + localListeners); - localListeners.forEach(l -> l.onChanged(value)); + localListeners.forEach(l -> l.onChanged(valueToNotify)); + } + + @GuardedBy("mLock") + private <T> Set<PluginManager.PluginChangeListener<T>> getListenersForUpdateLocked( + PluginType<T> type, String uniqueDisplayId) { + ListenersContainer<T> listenersContainer = getListenersContainerLocked(type); + Set<PluginManager.PluginChangeListener<T>> localListeners = new LinkedHashSet<>(); + // if GLOBAL value change we need to notify only listeners for displays that does not + // have display specific value + if (GLOBAL_ID.equals(uniqueDisplayId)) { + ValuesContainer<T> valuesContainer = getValuesContainerLocked(type); + Set<String> excludedDisplayIds = valuesContainer.getNonGlobalDisplaysLocked(); + listenersContainer.mListeners.forEach((localDisplayId, listeners) -> { + if (!excludedDisplayIds.contains(localDisplayId)) { + localListeners.addAll(listeners); + } + }); + } else { + localListeners.addAll( + listenersContainer.mListeners.getOrDefault(uniqueDisplayId, Set.of())); + } + return localListeners; } /** * Adds listener for PluginType. If storage already has value for this type, listener will * be notified immediately. */ - <T> void addListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) { + <T> void addListener(PluginType<T> type, String uniqueDisplayId, + PluginManager.PluginChangeListener<T> listener) { + if (GLOBAL_ID.equals(uniqueDisplayId)) { + Slog.d(TAG, "addListener ignored for GLOBAL_ID, type=" + type.mName); + return; + } T value = null; synchronized (mLock) { - ListenersContainer<T> container = getListenersContainerForTypeLocked(type); - if (container.mListeners.add(listener)) { - value = getValueForTypeLocked(type); + ListenersContainer<T> container = getListenersContainerLocked(type); + if (container.addListenerLocked(uniqueDisplayId, listener)) { + ValuesContainer<T> valuesContainer = getValuesContainerLocked(type); + value = valuesContainer.getValueLocked(uniqueDisplayId); } } if (value != null) { @@ -81,10 +139,15 @@ public class PluginStorage { /** * Removes listener */ - <T> void removeListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) { + <T> void removeListener(PluginType<T> type, String uniqueDisplayId, + PluginManager.PluginChangeListener<T> listener) { + if (GLOBAL_ID.equals(uniqueDisplayId)) { + Slog.d(TAG, "removeListener ignored for GLOBAL_ID, type=" + type.mName); + return; + } synchronized (mLock) { - ListenersContainer<T> container = getListenersContainerForTypeLocked(type); - container.mListeners.remove(listener); + ListenersContainer<T> container = getListenersContainerLocked(type); + container.removeListenerLocked(uniqueDisplayId, listener); } } @@ -92,53 +155,106 @@ public class PluginStorage { * Print the object's state and debug information into the given stream. */ void dump(PrintWriter pw) { - Map<PluginType<?>, Object> localValues; + Map<PluginType<?>, Map<String, Object>> localValues = new HashMap<>(); @SuppressWarnings("rawtypes") - Map<PluginType, Set> localListeners = new HashMap<>(); - List<PluginEventStorage.TimeFrame> timeFrames; + Map<PluginType, Map<String, Set>> localListeners = new HashMap<>(); + Map<String, List<PluginEventStorage.TimeFrame>> timeFrames = new HashMap<>(); synchronized (mLock) { - timeFrames = mPluginEventStorage.getTimeFrames(); - localValues = new HashMap<>(mValues); - mListeners.forEach((type, container) -> localListeners.put(type, container.mListeners)); + mPluginEventStorages.forEach((displayId, storage) -> { + timeFrames.put(displayId, storage.getTimeFrames()); + }); + mValues.forEach((type, valueContainer) -> { + localValues.put(type, new HashMap<>(valueContainer.mValues)); + }); + mListeners.forEach((type, container) -> { + localListeners.put(type, new HashMap<>(container.mListeners)); + }); } pw.println("PluginStorage:"); pw.println("values=" + localValues); pw.println("listeners=" + localListeners); pw.println("PluginEventStorage:"); - for (PluginEventStorage.TimeFrame timeFrame: timeFrames) { - timeFrame.dump(pw); + for (Map.Entry<String, List<PluginEventStorage.TimeFrame>> timeFrameEntry : + timeFrames.entrySet()) { + pw.println("TimeFrames for displayId=" + timeFrameEntry.getKey()); + for (PluginEventStorage.TimeFrame timeFrame : timeFrameEntry.getValue()) { + timeFrame.dump(pw); + } } } @GuardedBy("mLock") @SuppressWarnings("unchecked") - private <T> T getValueForTypeLocked(PluginType<T> type) { - Object value = mValues.get(type); - if (value == null) { - return null; - } else if (type.mType == value.getClass()) { - return (T) value; + private <T> ListenersContainer<T> getListenersContainerLocked(PluginType<T> type) { + ListenersContainer<?> container = mListeners.get(type); + if (container == null) { + ListenersContainer<T> lc = new ListenersContainer<>(); + mListeners.put(type, lc); + return lc; } else { - Slog.d(TAG, "getValueForType: unexpected value type=" + value.getClass().getName() - + ", expected=" + type.mType.getName()); - return null; + return (ListenersContainer<T>) container; } } @GuardedBy("mLock") @SuppressWarnings("unchecked") - private <T> ListenersContainer<T> getListenersContainerForTypeLocked(PluginType<T> type) { - ListenersContainer<?> container = mListeners.get(type); + private <T> ValuesContainer<T> getValuesContainerLocked(PluginType<T> type) { + ValuesContainer<?> container = mValues.get(type); if (container == null) { - ListenersContainer<T> lc = new ListenersContainer<>(); - mListeners.put(type, lc); - return lc; + ValuesContainer<T> vc = new ValuesContainer<>(); + mValues.put(type, vc); + return vc; } else { - return (ListenersContainer<T>) container; + return (ValuesContainer<T>) container; } } private static final class ListenersContainer<T> { - private final Set<PluginManager.PluginChangeListener<T>> mListeners = new LinkedHashSet<>(); + private final Map<String, Set<PluginManager.PluginChangeListener<T>>> mListeners = + new LinkedHashMap<>(); + + private boolean addListenerLocked( + String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener) { + Set<PluginManager.PluginChangeListener<T>> listenersForDisplay = + mListeners.computeIfAbsent(uniqueDisplayId, k -> new LinkedHashSet<>()); + return listenersForDisplay.add(listener); + } + + private void removeListenerLocked(String uniqueDisplayId, + PluginManager.PluginChangeListener<T> listener) { + Set<PluginManager.PluginChangeListener<T>> listenersForDisplay = mListeners.get( + uniqueDisplayId); + if (listenersForDisplay == null) { + return; + } + + listenersForDisplay.remove(listener); + + if (listenersForDisplay.isEmpty()) { + mListeners.remove(uniqueDisplayId); + } + } + } + + private static final class ValuesContainer<T> { + private final Map<String, T> mValues = new HashMap<>(); + + private void updateValueLocked(String uniqueDisplayId, @Nullable T value) { + if (value == null) { + mValues.remove(uniqueDisplayId); + } else { + mValues.put(uniqueDisplayId, value); + } + } + + private Set<String> getNonGlobalDisplaysLocked() { + Set<String> keys = new HashSet<>(mValues.keySet()); + keys.remove(GLOBAL_ID); + return keys; + } + + private @Nullable T getValueLocked(String displayId) { + return mValues.getOrDefault(displayId, mValues.get(GLOBAL_ID)); + } } } diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index be8a94149fdc..32b36bfb50e5 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -23,7 +23,6 @@ import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures; import static com.android.hardware.input.Flags.keyboardA11yShortcutControl; import static com.android.server.flags.Flags.newBugreportKeyboardShortcut; import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut; -import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts; import android.annotation.NonNull; import android.annotation.Nullable; @@ -37,6 +36,7 @@ import android.os.SystemProperties; import android.util.IndentingPrintWriter; import android.util.SparseArray; import android.view.KeyEvent; +import android.window.DesktopModeFlags; import com.android.internal.annotations.GuardedBy; @@ -233,7 +233,7 @@ final class InputGestureManager { KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS)); } - if (enableTaskResizingKeyboardShortcuts()) { + if (DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue()) { systemShortcuts.add(createKeyGesture( KeyEvent.KEYCODE_LEFT_BRACKET, KeyEvent.META_META_ON, diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java index 2e0bb4f88485..18f2f48b80a3 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.telecom.TelecomManager; import android.telephony.TelephonyCallback; @@ -38,6 +39,7 @@ import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemConfig; +import java.util.List; import java.util.function.Consumer; /** @@ -60,21 +62,35 @@ public class MediaProjectionStopController { private final TelephonyManager mTelephonyManager; private final AppOpsManager mAppOpsManager; private final PackageManager mPackageManager; - private final RoleManager mRoleManager; + private final RoleHolderProvider mRoleHolderProvider; private final ContentResolver mContentResolver; private boolean mIsInCall; private long mLastCallStartTimeMillis; + + @VisibleForTesting + interface RoleHolderProvider { + List<String> getRoleHoldersAsUser(String roleName, UserHandle user); + } + public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) { + this(context, stopReasonConsumer, + (roleName, user) -> context.getSystemService(RoleManager.class) + .getRoleHoldersAsUser(roleName, user)); + } + + @VisibleForTesting + MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer, + RoleHolderProvider roleHolderProvider) { mStopReasonConsumer = stopReasonConsumer; mKeyguardManager = context.getSystemService(KeyguardManager.class); mTelecomManager = context.getSystemService(TelecomManager.class); mTelephonyManager = context.getSystemService(TelephonyManager.class); mAppOpsManager = context.getSystemService(AppOpsManager.class); mPackageManager = context.getPackageManager(); - mRoleManager = context.getSystemService(RoleManager.class); mContentResolver = context.getContentResolver(); + mRoleHolderProvider = roleHolderProvider; } /** @@ -146,8 +162,9 @@ public class MediaProjectionStopController { Slog.v(TAG, "Continuing MediaProjection for package with OP_PROJECT_MEDIA AppOp "); return true; } - if (mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, - projectionGrant.userHandle).contains(projectionGrant.packageName)) { + if (mRoleHolderProvider.getRoleHoldersAsUser( + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, projectionGrant.userHandle) + .contains(projectionGrant.packageName)) { Slog.v(TAG, "Continuing MediaProjection for package holding app streaming role."); return true; } @@ -177,10 +194,6 @@ public class MediaProjectionStopController { */ public boolean isStartForbidden( MediaProjectionManagerService.MediaProjection projectionGrant) { - if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { - return false; - } - if (!mKeyguardManager.isKeyguardLocked()) { return false; } @@ -194,9 +207,6 @@ public class MediaProjectionStopController { @VisibleForTesting void onKeyguardLockedStateChanged(boolean isKeyguardLocked) { if (!isKeyguardLocked) return; - if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { - return; - } mStopReasonConsumer.accept(STOP_REASON_KEYGUARD); } diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index 5259bcc78311..f6c94a7d9a5a 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -32,10 +32,16 @@ import android.hardware.tv.mediaquality.AmbientBacklightColorFormat; import android.hardware.tv.mediaquality.DolbyAudioProcessing; import android.hardware.tv.mediaquality.DtsVirtualX; import android.hardware.tv.mediaquality.IMediaQuality; +import android.hardware.tv.mediaquality.IPictureProfileAdjustmentListener; +import android.hardware.tv.mediaquality.IPictureProfileChangedListener; +import android.hardware.tv.mediaquality.ISoundProfileAdjustmentListener; +import android.hardware.tv.mediaquality.ISoundProfileChangedListener; +import android.hardware.tv.mediaquality.ParamCapability; import android.hardware.tv.mediaquality.PictureParameter; import android.hardware.tv.mediaquality.PictureParameters; import android.hardware.tv.mediaquality.SoundParameter; import android.hardware.tv.mediaquality.SoundParameters; +import android.hardware.tv.mediaquality.VendorParamCapability; import android.media.quality.AmbientBacklightEvent; import android.media.quality.AmbientBacklightMetadata; import android.media.quality.AmbientBacklightSettings; @@ -105,6 +111,10 @@ public class MediaQualityService extends SystemService { private final BiMap<Long, String> mPictureProfileTempIdMap; private final BiMap<Long, String> mSoundProfileTempIdMap; private IMediaQuality mMediaQuality; + private IPictureProfileAdjustmentListener mPpAdjustmentListener; + private ISoundProfileAdjustmentListener mSpAdjustmentListener; + private IPictureProfileChangedListener mPpChangedListener; + private ISoundProfileChangedListener mSpChangedListener; private final HalAmbientBacklightCallback mHalAmbientBacklightCallback; private final Map<String, AmbientBacklightCallbackRecord> mCallbackRecords = new HashMap<>(); private final PackageManager mPackageManager; @@ -140,18 +150,104 @@ public class MediaQualityService extends SystemService { @Override public void onStart() { IBinder binder = ServiceManager.getService(IMediaQuality.DESCRIPTOR + "/default"); - if (binder != null) { - Slogf.d(TAG, "binder is not null"); - mMediaQuality = IMediaQuality.Stub.asInterface(binder); - if (mMediaQuality != null) { - try { - mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to set ambient backlight detector callback", e); + if (binder == null) { + Slogf.d(TAG, "Binder is null"); + return; + } + Slogf.d(TAG, "Binder is not null"); + + mPpAdjustmentListener = new IPictureProfileAdjustmentListener.Stub() { + @Override + public void onPictureProfileAdjusted( + android.hardware.tv.mediaquality.PictureProfile pictureProfile) + throws RemoteException { + // TODO + } + + @Override + public void onParamCapabilityChanged(long pictureProfileId, ParamCapability[] caps) + throws RemoteException { + // TODO + } + + @Override + public void onVendorParamCapabilityChanged(long pictureProfileId, + VendorParamCapability[] caps) throws RemoteException { + // TODO + } + + @Override + public void requestPictureParameters(long pictureProfileId) throws RemoteException { + // TODO + } + + @Override + public void onStreamStatusChanged(long pictureProfileId, byte status) + throws RemoteException { + // TODO + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; } + }; + mSpAdjustmentListener = new ISoundProfileAdjustmentListener.Stub() { + + @Override + public void onSoundProfileAdjusted( + android.hardware.tv.mediaquality.SoundProfile soundProfile) + throws RemoteException { + // TODO + } + + @Override + public void onParamCapabilityChanged(long soundProfileId, ParamCapability[] caps) + throws RemoteException { + // TODO + } + + @Override + public void onVendorParamCapabilityChanged(long soundProfileId, + VendorParamCapability[] caps) throws RemoteException { + // TODO + } + + @Override + public void requestSoundParameters(long soundProfileId) throws RemoteException { + // TODO + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + }; + + mMediaQuality = IMediaQuality.Stub.asInterface(binder); + if (mMediaQuality != null) { + try { + mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback); + mMediaQuality.setPictureProfileAdjustmentListener(mPpAdjustmentListener); + mMediaQuality.setSoundProfileAdjustmentListener(mSpAdjustmentListener); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set ambient backlight detector callback", e); } } + mPpChangedListener = IPictureProfileChangedListener.Stub.asInterface(binder); + mSpChangedListener = ISoundProfileChangedListener.Stub.asInterface(binder); + publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService()); } @@ -187,6 +283,30 @@ public class MediaQualityService extends SystemService { return pp; } + private void notifyHalOnPictureProfileChange(Long dbId, PersistableBundle params) { + // TODO: only notify HAL when the profile is active / being used + try { + mPpChangedListener.onPictureProfileChanged(convertToHalPictureProfile(dbId, + params)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify HAL on picture profile change.", e); + } + } + + private android.hardware.tv.mediaquality.PictureProfile convertToHalPictureProfile(Long id, + PersistableBundle params) { + PictureParameters pictureParameters = new PictureParameters(); + pictureParameters.pictureParameters = convertPersistableBundleToPictureParameterList( + params); + + android.hardware.tv.mediaquality.PictureProfile toReturn = + new android.hardware.tv.mediaquality.PictureProfile(); + toReturn.pictureProfileId = id; + toReturn.parameters = pictureParameters; + + return toReturn; + } + @Override public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) { Long dbId = mPictureProfileTempIdMap.getKey(id); @@ -207,6 +327,7 @@ public class MediaQualityService extends SystemService { null, values); notifyOnPictureProfileUpdated(mPictureProfileTempIdMap.getValue(dbId), getPictureProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid()); + notifyHalOnPictureProfileChange(dbId, pp.getParameters()); } private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) { @@ -240,6 +361,7 @@ public class MediaQualityService extends SystemService { notifyOnPictureProfileRemoved(mPictureProfileTempIdMap.getValue(dbId), toDelete, Binder.getCallingUid(), Binder.getCallingPid()); mPictureProfileTempIdMap.remove(dbId); + notifyHalOnPictureProfileChange(dbId, null); } } @@ -359,6 +481,10 @@ public class MediaQualityService extends SystemService { private PictureParameter[] convertPersistableBundleToPictureParameterList( PersistableBundle params) { + if (params == null) { + return null; + } + List<PictureParameter> pictureParams = new ArrayList<>(); if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) { pictureParams.add(PictureParameter.brightness(params.getLong( @@ -677,10 +803,6 @@ public class MediaQualityService extends SystemService { pictureParams.add(PictureParameter.colorTunerLuminanceFlesh(params.getInt( PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH))); } - if (params.containsKey(PictureQuality.PARAMETER_ACTIVE_PROFILE)) { - pictureParams.add(PictureParameter.activeProfile(params.getBoolean( - PictureQuality.PARAMETER_ACTIVE_PROFILE))); - } if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) { pictureParams.add(PictureParameter.pictureQualityEventType( (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE))); @@ -759,6 +881,28 @@ public class MediaQualityService extends SystemService { return sp; } + private void notifyHalOnSoundProfileChange(Long dbId, PersistableBundle params) { + // TODO: only notify HAL when the profile is active / being used + try { + mSpChangedListener.onSoundProfileChanged(convertToHalSoundProfile(dbId, params)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify HAL on sound profile change.", e); + } + } + + private android.hardware.tv.mediaquality.SoundProfile convertToHalSoundProfile(Long id, + PersistableBundle params) { + SoundParameters soundParameters = new SoundParameters(); + soundParameters.soundParameters = convertPersistableBundleToSoundParameterList(params); + + android.hardware.tv.mediaquality.SoundProfile toReturn = + new android.hardware.tv.mediaquality.SoundProfile(); + toReturn.soundProfileId = id; + toReturn.parameters = soundParameters; + + return toReturn; + } + @Override public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) { Long dbId = mSoundProfileTempIdMap.getKey(id); @@ -778,6 +922,7 @@ public class MediaQualityService extends SystemService { db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values); notifyOnSoundProfileUpdated(mSoundProfileTempIdMap.getValue(dbId), getSoundProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid()); + notifyHalOnSoundProfileChange(dbId, sp.getParameters()); } private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) { @@ -810,6 +955,7 @@ public class MediaQualityService extends SystemService { notifyOnSoundProfileRemoved(mSoundProfileTempIdMap.getValue(dbId), toDelete, Binder.getCallingUid(), Binder.getCallingPid()); mSoundProfileTempIdMap.remove(dbId); + notifyHalOnSoundProfileChange(dbId, null); } } @@ -929,6 +1075,9 @@ public class MediaQualityService extends SystemService { private SoundParameter[] convertPersistableBundleToSoundParameterList( PersistableBundle params) { //TODO: set EqualizerDetail + if (params == null) { + return null; + } List<SoundParameter> soundParams = new ArrayList<>(); if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) { soundParams.add(SoundParameter.balance(params.getInt( @@ -973,10 +1122,6 @@ public class MediaQualityService extends SystemService { soundParams.add(SoundParameter.downmixMode((byte) params.getInt( SoundQuality.PARAMETER_DOWN_MIX_MODE))); } - if (params.containsKey(SoundQuality.PARAMETER_ACTIVE_PROFILE)) { - soundParams.add(SoundParameter.activeProfile(params.getBoolean( - SoundQuality.PARAMETER_ACTIVE_PROFILE))); - } if (params.containsKey(SoundQuality.PARAMETER_SOUND_STYLE)) { soundParams.add(SoundParameter.soundStyle((byte) params.getInt( SoundQuality.PARAMETER_SOUND_STYLE))); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java index 25c07500b891..872ab595994b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java @@ -28,6 +28,9 @@ public abstract class WallpaperManagerInternal { */ public abstract void onDisplayReady(int displayId); + /** Notifies when display stop showing system decorations and wallpaper. */ + public abstract void onDisplayRemoveSystemDecorations(int displayId); + /** Notifies when the screen finished turning on and is visible to the user. */ public abstract void onScreenTurnedOn(int displayId); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 83e146df3e53..db530e728a1a 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -667,71 +667,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub @Override public void onDisplayRemoved(int displayId) { - synchronized (mLock) { - if (enableConnectedDisplaysWallpaper()) { - // There could be at most 2 wallpaper connections per display: - // 1. system & lock are the same: mLastWallpaper - // 2. system, lock are different: mLastWallpaper, mLastLockWallpaper - // 3. fallback used as both system & lock wallpaper: mFallbackWallpaper - // 4. fallback used as lock only wallpaper: mFallbackWallpaper, - // mLastWallpaper - // 5. fallback used as system only wallpaper: mFallbackWallpaper, - // mLastLockWallpaper - List<WallpaperData> pendingDisconnectWallpapers = new ArrayList<>(); - if (mLastWallpaper != null && mLastWallpaper.connection != null - && mLastWallpaper.connection.containsDisplay(displayId)) { - pendingDisconnectWallpapers.add(mLastWallpaper); - } - if (mLastLockWallpaper != null && mLastLockWallpaper.connection != null - && mLastLockWallpaper.connection.containsDisplay(displayId)) { - pendingDisconnectWallpapers.add(mLastLockWallpaper); - } - if (mFallbackWallpaper != null && mFallbackWallpaper.connection != null - && mFallbackWallpaper.connection.containsDisplay(displayId)) { - pendingDisconnectWallpapers.add(mFallbackWallpaper); - } - for (int i = 0; i < pendingDisconnectWallpapers.size(); i++) { - WallpaperData wallpaper = pendingDisconnectWallpapers.get(i); - DisplayConnector displayConnector = - wallpaper.connection.getDisplayConnectorOrCreate(displayId); - if (displayConnector == null) { - Slog.w(TAG, - "Fail to disconnect wallpaper upon display removal"); - return; - } - displayConnector.disconnectLocked(wallpaper.connection); - wallpaper.connection.removeDisplayConnector(displayId); - } - } else { - if (mLastWallpaper != null) { - WallpaperData targetWallpaper = null; - if (mLastWallpaper.connection != null - && mLastWallpaper.connection.containsDisplay(displayId)) { - targetWallpaper = mLastWallpaper; - } else if (mFallbackWallpaper != null - && mFallbackWallpaper.connection != null - && mFallbackWallpaper.connection.containsDisplay( - displayId)) { - targetWallpaper = mFallbackWallpaper; - } - if (targetWallpaper == null) return; - DisplayConnector connector = - targetWallpaper.connection.getDisplayConnectorOrCreate( - displayId); - if (connector == null) return; - connector.disconnectLocked(targetWallpaper.connection); - targetWallpaper.connection.removeDisplayConnector(displayId); - } - } - - mWallpaperDisplayHelper.removeDisplayData(displayId); - - for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) { - final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks = - mColorsChangedListeners.valueAt(i); - callbacks.delete(displayId); - } - } + onDisplayRemovedInternal(displayId); } @Override @@ -1705,6 +1641,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } @Override + public void onDisplayRemoveSystemDecorations(int displayId) { + // The display mirroring starts. The handling logic is the same as when removing a + // display. + onDisplayRemovedInternal(displayId); + } + + @Override public void onScreenTurnedOn(int displayId) { notifyScreenTurnedOn(displayId); } @@ -4063,6 +4006,78 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + // This method may be called even if the display is not being removed from the system. + // This can be called when the display is removed, or when the display system decorations are + // removed to start mirroring. + private void onDisplayRemovedInternal(int displayId) { + synchronized (mLock) { + if (enableConnectedDisplaysWallpaper()) { + // There could be at most 2 wallpaper connections per display: + // 1. system & lock are the same: mLastWallpaper + // 2. system, lock are different: mLastWallpaper, mLastLockWallpaper + // 3. fallback used as both system & lock wallpaper: mFallbackWallpaper + // 4. fallback used as lock only wallpaper: mFallbackWallpaper, + // mLastWallpaper + // 5. fallback used as system only wallpaper: mFallbackWallpaper, + // mLastLockWallpaper + List<WallpaperData> pendingDisconnectWallpapers = new ArrayList<>(); + if (mLastWallpaper != null && mLastWallpaper.connection != null + && mLastWallpaper.connection.containsDisplay(displayId)) { + pendingDisconnectWallpapers.add(mLastWallpaper); + } + if (mLastLockWallpaper != null && mLastLockWallpaper.connection != null + && mLastLockWallpaper.connection.containsDisplay(displayId)) { + pendingDisconnectWallpapers.add(mLastLockWallpaper); + } + if (mFallbackWallpaper != null && mFallbackWallpaper.connection != null + && mFallbackWallpaper.connection.containsDisplay(displayId)) { + pendingDisconnectWallpapers.add(mFallbackWallpaper); + } + for (int i = 0; i < pendingDisconnectWallpapers.size(); i++) { + WallpaperData wallpaper = pendingDisconnectWallpapers.get(i); + DisplayConnector displayConnector = + wallpaper.connection.getDisplayConnectorOrCreate(displayId); + if (displayConnector == null) { + Slog.w(TAG, + "Fail to disconnect wallpaper upon display removes system " + + "decorations"); + return; + } + displayConnector.disconnectLocked(wallpaper.connection); + wallpaper.connection.removeDisplayConnector(displayId); + } + } else { + if (mLastWallpaper != null) { + WallpaperData targetWallpaper = null; + if (mLastWallpaper.connection != null + && mLastWallpaper.connection.containsDisplay(displayId)) { + targetWallpaper = mLastWallpaper; + } else if (mFallbackWallpaper != null + && mFallbackWallpaper.connection != null + && mFallbackWallpaper.connection.containsDisplay( + displayId)) { + targetWallpaper = mFallbackWallpaper; + } + if (targetWallpaper == null) return; + DisplayConnector connector = + targetWallpaper.connection.getDisplayConnectorOrCreate( + displayId); + if (connector == null) return; + connector.disconnectLocked(targetWallpaper.connection); + targetWallpaper.connection.removeDisplayConnector(displayId); + } + } + + mWallpaperDisplayHelper.removeDisplayData(displayId); + + for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) { + final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks = + mColorsChangedListeners.valueAt(i); + callbacks.delete(displayId); + } + } + } + void saveSettingsLocked(int userId) { TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); t.traceBegin("WPMS.saveSettingsLocked-" + userId); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 064ef1aa0eff..89b46bc4eba4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -46,6 +46,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.isFloating; import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; @@ -8376,6 +8377,7 @@ final class ActivityRecord extends WindowToken { mConfigurationSeq = Math.max(++mConfigurationSeq, 1); getResolvedOverrideConfiguration().seq = mConfigurationSeq; + // TODO(b/392069771): Move to AppCompatSandboxingPolicy. // Sandbox max bounds by setting it to the activity bounds, if activity is letterboxed, or // has or will have mAppCompatDisplayInsets for size compat. Also forces an activity to be // sandboxed or not depending upon the configuration settings. @@ -8404,6 +8406,20 @@ final class ActivityRecord extends WindowToken { resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } + // Sandbox activity bounds in freeform to app bounds to force app to display within the + // container. This prevents UI cropping when activities can draw below insets which are + // normally excluded from appBounds before targetSDK < 35 + // (see ConfigurationContainer#applySizeOverrideIfNeeded). + if (isFloating(parentWindowingMode)) { + Rect appBounds = resolvedConfig.windowConfiguration.getAppBounds(); + if (appBounds == null || appBounds.isEmpty()) { + // When there is no override bounds, the activity will inherit the bounds from + // parent. + appBounds = mResolveConfigHint.mParentAppBoundsOverride; + } + resolvedConfig.windowConfiguration.setBounds(appBounds); + } + applySizeOverrideIfNeeded( mDisplayContent, info.applicationInfo, diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java index 35fa39dab900..d994a1904a14 100644 --- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java @@ -180,10 +180,7 @@ class AppCompatOrientationPolicy { return true; } - final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy - .getAppCompatCameraPolicy(mActivityRecord); - if (cameraPolicy != null - && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) { + if (AppCompatCameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) { Slog.w(TAG, "Ignoring orientation update to " + screenOrientationToString(requestedOrientation) + " due to camera compat treatment for " + mActivityRecord); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e1a50a93edcc..5b1619995529 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1910,6 +1910,11 @@ public class DisplayPolicy { if (statusBar != null) { statusBar.onDisplayRemoveSystemDecorations(displayId); } + final WallpaperManagerInternal wpMgr = + LocalServices.getService(WallpaperManagerInternal.class); + if (wpMgr != null) { + wpMgr.onDisplayRemoveSystemDecorations(displayId); + } }); } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index bcfaa3947e74..59ca79c7ffc3 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -361,7 +361,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { controlTarget = mDisplayContent.getImeHostOrFallback( ((InsetsControlTarget) imeInsetsTarget).getWindow()); - if (controlTarget != imeInsetsTarget) { + if (controlTarget != null && controlTarget != imeInsetsTarget) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 37cc0d22c063..27683b2fcff2 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1504,16 +1504,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } // Update the input-sink (touch-blocking) state now that the animation is finished. - SurfaceControl.Transaction inputSinkTransaction = null; + boolean scheduleAnimation = false; for (int i = 0; i < mParticipants.size(); ++i) { final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); if (ar == null || !ar.isVisible() || ar.getParent() == null) continue; - if (inputSinkTransaction == null) { - inputSinkTransaction = ar.mWmService.mTransactionFactory.get(); - } - ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction); + scheduleAnimation = true; + ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(ar.getPendingTransaction()); } - if (inputSinkTransaction != null) inputSinkTransaction.apply(); + // To apply pending transactions. + if (scheduleAnimation) mController.mAtm.mWindowManager.scheduleAnimationLocked(); // Always schedule stop processing when transition finishes because activities don't // stop while they are in a transition thus their stop could still be pending. diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index e761e024b3ca..883d8f95b612 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1680,26 +1680,27 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * Sets the specified orientation of this container. It percolates this change upward along the * hierarchy to let each level of the hierarchy a chance to respond to it. * - * @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}. + * @param requestedOrientation the specified orientation. Needs to be one of + * {@link ScreenOrientation}. * @param requestingContainer the container which orientation request has changed. Mostly used * to ensure it gets correct configuration. * @return the resolved override orientation of this window container. */ @ScreenOrientation - int setOrientation(@ScreenOrientation int orientation, + int setOrientation(@ScreenOrientation int requestedOrientation, @Nullable WindowContainer requestingContainer) { - if (getOverrideOrientation() == orientation) { - return orientation; + if (getOverrideOrientation() == requestedOrientation) { + return requestedOrientation; } - setOverrideOrientation(orientation); + setOverrideOrientation(requestedOrientation); final WindowContainer parent = getParent(); if (parent == null) { - return orientation; + return requestedOrientation; } // The derived class can return a result that is different from the given orientation. - final int resolvedOrientation = getOverrideOrientation(); + final int actualOverrideOrientation = getOverrideOrientation(); if (getConfiguration().orientation != getRequestedConfigurationOrientation( - false /* forDisplay */, resolvedOrientation) + false /* forDisplay */, actualOverrideOrientation) // Update configuration directly only if the change won't be dispatched from // ancestor. This prevents from computing intermediate configuration when the // parent also needs to be updated from the ancestor. E.g. the app requests @@ -1707,12 +1708,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // the task can be updated to portrait first so the configuration can be // computed in a consistent environment. && (inMultiWindowMode() - || !handlesOrientationChangeFromDescendant(orientation))) { + || !handlesOrientationChangeFromDescendant(requestedOrientation))) { // Resolve the requested orientation. onConfigurationChanged(parent.getConfiguration()); } onDescendantOrientationChanged(requestingContainer); - return resolvedOrientation; + return actualOverrideOrientation; } @ScreenOrientation diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java index 073ee31ddd60..4f9859d33d74 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionService.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java @@ -51,7 +51,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import com.android.server.pm.UserManagerInternal; import java.io.FileDescriptor; @@ -62,17 +61,17 @@ import java.util.List; public class SupervisionService extends ISupervisionManager.Stub { private static final String LOG_TAG = "SupervisionService"; - private final Context mContext; - // TODO(b/362756788): Does this need to be a LockGuard lock? private final Object mLockDoNoUseDirectly = new Object(); @GuardedBy("getLockObject()") private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>(); + private final Context mContext; private final DevicePolicyManagerInternal mDpmInternal; private final PackageManager mPackageManager; private final UserManagerInternal mUserManagerInternal; + final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); public SupervisionService(Context context) { mContext = context.createAttributionContext(LOG_TAG); @@ -82,6 +81,12 @@ public class SupervisionService extends ISupervisionManager.Stub { mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener()); } + /** + * Returns whether supervision is enabled for the given user. + * + * <p>Supervision is automatically enabled when the supervision app becomes the profile owner or + * explicitly enabled via an internal call to {@link #setSupervisionEnabledForUser}. + */ @Override public boolean isSupervisionEnabledForUser(@UserIdInt int userId) { if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { @@ -92,6 +97,20 @@ public class SupervisionService extends ISupervisionManager.Stub { } } + /** + * Returns the package name of the active supervision app or null if supervision is disabled. + */ + @Override + @Nullable + public String getActiveSupervisionAppPackage(@UserIdInt int userId) { + if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { + enforcePermission(INTERACT_ACROSS_USERS); + } + synchronized (getLockObject()) { + return getUserDataLocked(userId).supervisionAppPackage; + } + } + @Override public void onShellCommand( @Nullable FileDescriptor in, @@ -140,35 +159,53 @@ public class SupervisionService extends ISupervisionManager.Stub { return data; } - void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) { + /** + * Sets supervision as enabled or disabled for the given user and, in case supervision is being + * enabled, the package of the active supervision app. + */ + private void setSupervisionEnabledForUser( + @UserIdInt int userId, boolean enabled, @Nullable String supervisionAppPackage) { synchronized (getLockObject()) { - getUserDataLocked(userId).supervisionEnabled = enabled; + SupervisionUserData data = getUserDataLocked(userId); + data.supervisionEnabled = enabled; + data.supervisionAppPackage = enabled ? supervisionAppPackage : null; } } - /** Ensures that supervision is enabled when supervision app is the profile owner. */ + /** Ensures that supervision is enabled when the supervision app is the profile owner. */ private void syncStateWithDevicePolicyManager(@UserIdInt int userId) { - if (isProfileOwner(userId)) { - setSupervisionEnabledForUser(userId, true); + final ComponentName po = + mDpmInternal != null ? mDpmInternal.getProfileOwnerAsUser(userId) : null; + + if (po != null && po.getPackageName().equals(getSystemSupervisionPackage())) { + setSupervisionEnabledForUser(userId, true, po.getPackageName()); + } else if (po != null && po.equals(getSupervisionProfileOwnerComponent())) { + // TODO(b/392071637): Consider not enabling supervision in case profile owner is given + // to the legacy supervision profile owner component. + setSupervisionEnabledForUser(userId, true, po.getPackageName()); } else { // TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner. // This might only be possible after introducing specific and public APIs to enable - // supervision. - setSupervisionEnabledForUser(userId, false); + // and disable supervision. + setSupervisionEnabledForUser(userId, false, /* supervisionAppPackage= */ null); } } - /** Returns whether the supervision app has profile owner status. */ - private boolean isProfileOwner(@UserIdInt int userId) { - ComponentName profileOwner = - mDpmInternal != null ? mDpmInternal.getProfileOwnerAsUser(userId) : null; - return profileOwner != null && isSupervisionAppPackage(profileOwner.getPackageName()); + /** + * Returns the {@link ComponentName} of the supervision profile owner component. + * + * <p>This component is used to give GMS Kids Module permission to supervise the device and may + * still be active during the transition to the {@code SYSTEM_SUPERVISION} role. + */ + private ComponentName getSupervisionProfileOwnerComponent() { + return ComponentName.unflattenFromString( + mContext.getResources() + .getString(R.string.config_defaultSupervisionProfileOwnerComponent)); } - /** Returns whether the given package name belongs to the supervision role holder. */ - private boolean isSupervisionAppPackage(String packageName) { - return packageName.equals( - mContext.getResources().getString(R.string.config_systemSupervision)); + /** Returns the package assigned to the {@code SYSTEM_SUPERVISION} role. */ + private String getSystemSupervisionPackage() { + return mContext.getResources().getString(R.string.config_systemSupervision); } /** Enforces that the caller has the given permission. */ @@ -228,19 +265,21 @@ public class SupervisionService extends ISupervisionManager.Stub { } } - final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); - private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal { @Override public boolean isActiveSupervisionApp(int uid) { - String[] packages = mPackageManager.getPackagesForUid(uid); - if (packages == null) { + int userId = UserHandle.getUserId(uid); + String supervisionAppPackage = getActiveSupervisionAppPackage(userId); + if (supervisionAppPackage == null) { return false; } - for (var packageName : packages) { - if (SupervisionService.this.isSupervisionAppPackage(packageName)) { - int userId = UserHandle.getUserId(uid); - return SupervisionService.this.isSupervisionEnabledForUser(userId); + + String[] packages = mPackageManager.getPackagesForUid(uid); + if (packages != null) { + for (var packageName : packages) { + if (supervisionAppPackage.equals(packageName)) { + return true; + } } } return false; @@ -253,7 +292,8 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) { - SupervisionService.this.setSupervisionEnabledForUser(userId, enabled); + SupervisionService.this.setSupervisionEnabledForUser( + userId, enabled, getSystemSupervisionPackage()); } @Override diff --git a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java index 2adaae3943f1..976642bd563d 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java @@ -32,16 +32,18 @@ public class SupervisionServiceShellCommand extends ShellCommand { return handleDefaultCommands(null); } switch (cmd) { - case "enable": return setEnabled(true); - case "disable": return setEnabled(false); - default: return handleDefaultCommands(cmd); + case "enable": + return setEnabled(true); + case "disable": + return setEnabled(false); + default: + return handleDefaultCommands(cmd); } } private int setEnabled(boolean enabled) { - final var pw = getOutPrintWriter(); final var userId = UserHandle.parseUserArg(getNextArgRequired()); - mService.setSupervisionEnabledForUser(userId, enabled); + mService.mInternal.setSupervisionEnabledForUser(userId, enabled); return 0; } diff --git a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java index 1dd48f581bf4..06acb91509a1 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java @@ -26,6 +26,7 @@ import android.util.IndentingPrintWriter; public class SupervisionUserData { public final @UserIdInt int userId; public boolean supervisionEnabled; + @Nullable public String supervisionAppPackage; public boolean supervisionLockScreenEnabled; @Nullable public PersistableBundle supervisionLockScreenOptions; @@ -38,6 +39,7 @@ public class SupervisionUserData { pw.println("User " + userId + ":"); pw.increaseIndent(); pw.println("supervisionEnabled: " + supervisionEnabled); + pw.println("supervisionAppPackage: " + supervisionAppPackage); pw.println("supervisionLockScreenEnabled: " + supervisionLockScreenEnabled); pw.println("supervisionLockScreenOptions: " + supervisionLockScreenOptions); pw.decreaseIndent(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt index 01061f16c279..d9224eaf66ca 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt @@ -29,6 +29,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever private val TEST_PLUGIN_TYPE = PluginType(Int::class.java, "test_type") +private val DISPLAY_ID = "display_id" @SmallTest class PluginManagerTest { @@ -62,18 +63,18 @@ class PluginManagerTest { fun testSubscribe() { val pluginManager = createPluginManager() - pluginManager.subscribe(TEST_PLUGIN_TYPE, mockListener) + pluginManager.subscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) - verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, mockListener) + verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) } @Test fun testUnsubscribe() { val pluginManager = createPluginManager() - pluginManager.unsubscribe(TEST_PLUGIN_TYPE, mockListener) + pluginManager.unsubscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) - verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, mockListener) + verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener) } private fun createPluginManager(enabled: Boolean = true): PluginManager { diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt index 218e34134e93..8eb3e9fbf9b0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt @@ -23,6 +23,8 @@ import org.junit.Test private val TEST_PLUGIN_TYPE1 = PluginType(String::class.java, "test_type1") private val TEST_PLUGIN_TYPE2 = PluginType(String::class.java, "test_type2") +private val DISPLAY_ID_1 = "display_1" +private val DISPLAY_ID_2 = "display_2" @SmallTest class PluginStorageTest { @@ -33,9 +35,9 @@ class PluginStorageTest { fun testUpdateValue() { val type1Value = "value1" val testChangeListener = TestPluginChangeListener<String>() - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) assertThat(testChangeListener.receivedValue).isEqualTo(type1Value) } @@ -44,9 +46,9 @@ class PluginStorageTest { fun testAddListener() { val type1Value = "value1" val testChangeListener = TestPluginChangeListener<String>() - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) assertThat(testChangeListener.receivedValue).isEqualTo(type1Value) } @@ -55,10 +57,10 @@ class PluginStorageTest { fun testRemoveListener() { val type1Value = "value1" val testChangeListener = TestPluginChangeListener<String>() - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) - storage.removeListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) + storage.removeListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) assertThat(testChangeListener.receivedValue).isNull() } @@ -68,10 +70,10 @@ class PluginStorageTest { val type1Value = "value1" val type2Value = "value2" val testChangeListener = TestPluginChangeListener<String>() - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) - storage.updateValue(TEST_PLUGIN_TYPE2, type2Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, type2Value) - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener) assertThat(testChangeListener.receivedValue).isEqualTo(type1Value) } @@ -81,15 +83,62 @@ class PluginStorageTest { val type1Value = "value1" val testChangeListener1 = TestPluginChangeListener<String>() val testChangeListener2 = TestPluginChangeListener<String>() - storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener1) - storage.addListener(TEST_PLUGIN_TYPE2, testChangeListener2) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, testChangeListener2) - storage.updateValue(TEST_PLUGIN_TYPE1, type1Value) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value) assertThat(testChangeListener2.receivedValue).isNull() } + @Test + fun testUpdateGlobal_noDisplaySpecificValue() { + val type1Value = "value1" + val testChangeListener1 = TestPluginChangeListener<String>() + val testChangeListener2 = TestPluginChangeListener<String>() + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2) + + storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1Value) + + assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value) + assertThat(testChangeListener2.receivedValue).isEqualTo(type1Value) + } + + @Test + fun testUpdateGlobal_withDisplaySpecificValue() { + val type1Value = "value1" + val type1GlobalValue = "value1Global" + val testChangeListener1 = TestPluginChangeListener<String>() + val testChangeListener2 = TestPluginChangeListener<String>() + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2) + + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) + storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue) + + assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value) + assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue) + } + + @Test + fun testUpdateGlobal_withDisplaySpecificValueRemoved() { + val type1Value = "value1" + val type1GlobalValue = "value1Global" + val testChangeListener1 = TestPluginChangeListener<String>() + val testChangeListener2 = TestPluginChangeListener<String>() + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1) + storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2) + + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value) + storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue) + storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, null) + + assertThat(testChangeListener1.receivedValue).isEqualTo(type1GlobalValue) + assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue) + } + private class TestPluginChangeListener<T> : PluginChangeListener<T> { var receivedValue: T? = null diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 8dc8c14f8948..cb52f1849b5b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -151,6 +151,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.flag.util.FlagSetException; @@ -192,7 +193,9 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; +import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; @@ -435,6 +438,7 @@ public final class AlarmManagerServiceTest { private void disableFlagsNotSetByAnnotation() { try { mSetFlagsRule.disableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS); + mSetFlagsRule.disableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND); } catch (FlagSetException fse) { // Expected if the test about to be run requires this enabled. } @@ -948,6 +952,28 @@ public final class AlarmManagerServiceTest { } @Test + @EnableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND) + public void testWakelockOrdering() throws Exception { + final long triggerTime = mNowElapsedTest + 5000; + final PendingIntent alarmPi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi); + + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + + final InOrder inOrder = Mockito.inOrder(alarmPi, mWakeLock); + inOrder.verify(mWakeLock).acquire(); + + final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor = + ArgumentCaptor.forClass(PendingIntent.OnFinished.class); + inOrder.verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), + onFinishedCaptor.capture(), any(Handler.class), isNull(), any()); + onFinishedCaptor.getValue().onSendFinished(alarmPi, null, 0, null, null); + + inOrder.verify(mWakeLock).release(); + } + + @Test public void testMinFuturityCoreUid() { setDeviceConfigLong(KEY_MIN_FUTURITY, 10L); assertEquals(10, mService.mConstants.MIN_FUTURITY); diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 793ba7bc89bd..b92afc5c0ca7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -960,6 +960,74 @@ public class WallpaperManagerServiceTests { .isEqualTo(FLAG_LOCK | FLAG_SYSTEM); } + // Verify a secondary display removes system decorations started + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayRemoveSystemDecorations_sameSystemAndLockWallpaper_shouldDetachWallpaperServiceOnce() + throws Exception { + // GIVEN the same wallpaper used for the lock and system. + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + wallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN wallpaper connections have been established for displayID, 2. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + // Save displayConnector for displayId 2 before display removal. + WallpaperManagerService.DisplayConnector displayConnector = + wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + + // WHEN displayId, 2, is removed. + wallpaperManagerInternal.onDisplayRemoveSystemDecorations(testDisplayId); + + // Then the wallpaper connection for displayId, 2, is detached. + verify(mockIWallpaperService).detach(eq(displayConnector.mToken)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayRemoveSystemDecorations_differentSystemAndLockWallpapers_shouldDetachWallpaperServiceTwice() + throws Exception { + // GIVEN different wallpapers used for the lock and system. + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(), + FLAG_LOCK, testUserId); + final WallpaperData systemWallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, + testUserId); + final WallpaperData lockWallpaper = mService.getCurrentWallpaperData(FLAG_LOCK, + testUserId); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + systemWallpaper.connection.mService = mockIWallpaperService; + lockWallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN wallpaper connections have been established for displayID, 2. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + // Save displayConnectors for displayId 2 before display removal. + WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector = + systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + WallpaperManagerService.DisplayConnector lockWallpaperDisplayConnector = + lockWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + + // WHEN displayId, 2, is removed. + wallpaperManagerInternal.onDisplayRemoveSystemDecorations(testDisplayId); + + // Then the system wallpaper connection for displayId, 2, is detached. + verify(mockIWallpaperService).detach(eq(systemWallpaperDisplayConnector.mToken)); + // Then the lock wallpaper connection for displayId, 2, is detached. + verify(mockIWallpaperService).detach(eq(lockWallpaperDisplayConnector.mToken)); + } + // Verify a secondary display removes system decorations ended + // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for // non-current user must not bind to wallpaper service. private void verifyNoConnectionBeforeLastUser(int lastUserId) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java index ecc48bfc40e4..f55ca0c0b12b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java @@ -57,6 +57,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; +import com.android.server.accessibility.autoclick.AutoclickController; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.MagnificationGestureHandler; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java index acce813ff659..f02bdae1d9e6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.accessibility; +package com.android.server.accessibility.autoclick; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -39,6 +39,8 @@ import android.view.MotionEvent; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import com.android.server.accessibility.AccessibilityTraceManager; + import org.junit.After; import org.junit.Before; import org.junit.Rule; diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java index 8d3eef4a3168..cbe2bfb26cd6 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java @@ -28,8 +28,10 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; @@ -95,6 +97,10 @@ public class AbsoluteVolumeBehaviorTest { when(mResources.getBoolean(com.android.internal.R.bool.config_useFixedVolume)) .thenReturn(false); + when(mContext.checkCallingOrSelfPermission( + Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSystemServer = new NoOpSystemServerAdapter(); mSettingsAdapter = new NoOpSettingsAdapter(); diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 3ced56a04138..a58a9cd2a28f 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -34,7 +34,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -53,15 +52,11 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; -import android.Manifest; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; import android.app.AppOpsManager; -import android.app.Instrumentation; import android.app.KeyguardManager; -import android.app.role.RoleManager; -import android.companion.AssociationRequest; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -76,11 +71,9 @@ import android.media.projection.StopReason; import android.os.Binder; import android.os.IBinder; import android.os.Looper; -import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.test.TestLooper; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -99,7 +92,6 @@ import com.android.server.testutils.OffsettableClock; import com.android.server.wm.WindowManagerInternal; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -110,7 +102,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -292,8 +283,6 @@ public class MediaProjectionManagerServiceTest { assertThat(stoppedCallback2).isFalse(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked() throws Exception { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); @@ -308,8 +297,6 @@ public class MediaProjectionManagerServiceTest { assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_packageAllowlisted() throws NameNotFoundException { @@ -325,8 +312,6 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.getActiveProjectionInfo()).isNotNull(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_AppOpMediaProjection() throws NameNotFoundException { @@ -347,50 +332,6 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.getActiveProjectionInfo()).isNotNull(); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) - @Test - public void testCreateProjection_keyguardLocked_RoleHeld() { - runWithRole( - AssociationRequest.DEVICE_PROFILE_APP_STREAMING, - () -> { - try { - mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED; - doReturn(mAppInfo) - .when(mPackageManager) - .getApplicationInfoAsUser( - anyString(), - any(ApplicationInfoFlags.class), - any(UserHandle.class)); - MediaProjectionManagerService.MediaProjection projection = - mService.createProjectionInternal( - Process.myUid(), - mContext.getPackageName(), - TYPE_MIRRORING, - /* isPermanentGrant= */ false, - UserHandle.CURRENT, - DEFAULT_DISPLAY); - doReturn(true).when(mKeyguardManager).isKeyguardLocked(); - doReturn(PackageManager.PERMISSION_DENIED) - .when(mPackageManager) - .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName); - - projection.start(mIMediaProjectionCallback); - projection.notifyVirtualDisplayCreated(10); - - // The projection was started because it was allowed to capture the - // keyguard. - assertWithMessage("Failed to run projection") - .that(mService.getActiveProjectionInfo()) - .isNotNull(); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - }); - } - - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_screenshareProtectionsDisabled() throws NameNotFoundException { @@ -416,8 +357,6 @@ public class MediaProjectionManagerServiceTest { } } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_noDisplayCreated() throws NameNotFoundException { @@ -509,8 +448,6 @@ public class MediaProjectionManagerServiceTest { assertThat(secondProjection).isNotEqualTo(projection); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testReuseProjection_keyguardNotLocked_startConsentDialog() throws NameNotFoundException { @@ -527,8 +464,6 @@ public class MediaProjectionManagerServiceTest { verify(mContext).startActivityAsUser(any(), any()); } - @EnableFlags(android.companion.virtualdevice.flags - .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testReuseProjection_keyguardLocked_noConsentDialog() throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); @@ -1302,48 +1237,6 @@ public class MediaProjectionManagerServiceTest { return mService.getProjectionInternal(UID, PACKAGE_NAME); } - /** - * Run the provided block giving the current context's package the provided role. - */ - @SuppressWarnings("SameParameterValue") - private void runWithRole(String role, Runnable block) { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - String packageName = mContext.getPackageName(); - UserHandle user = instrumentation.getTargetContext().getUser(); - RoleManager roleManager = Objects.requireNonNull( - mContext.getSystemService(RoleManager.class)); - try { - CountDownLatch latch = new CountDownLatch(1); - instrumentation.getUiAutomation().adoptShellPermissionIdentity( - Manifest.permission.MANAGE_ROLE_HOLDERS, - Manifest.permission.BYPASS_ROLE_QUALIFICATION); - - roleManager.setBypassingRoleQualification(true); - roleManager.addRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), success -> { - if (success) { - latch.countDown(); - } else { - Assert.fail("Couldn't set role for test (failure) " + role); - } - }); - assertWithMessage("Couldn't set role for test (timeout) : " + role) - .that(latch.await(1, TimeUnit.SECONDS)).isTrue(); - block.run(); - - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - roleManager.removeRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), (aBool) -> {}); - roleManager.setBypassingRoleQualification(false); - instrumentation.getUiAutomation() - .dropShellPermissionIdentity(); - } - } - private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub { CountDownLatch mLatch = new CountDownLatch(1); @Override diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java index 379079a0018c..10ac0495d69a 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java @@ -22,7 +22,6 @@ import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_ import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -37,13 +36,10 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.Manifest; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; -import android.app.Instrumentation; import android.app.KeyguardManager; -import android.app.role.RoleManager; import android.companion.AssociationRequest; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -69,7 +65,6 @@ import com.android.server.SystemConfig; import com.android.server.wm.WindowManagerInternal; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -79,9 +74,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.Objects; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import java.util.List; import java.util.function.Consumer; /** @@ -123,6 +116,8 @@ public class MediaProjectionStopControllerTest { private KeyguardManager mKeyguardManager; @Mock private TelecomManager mTelecomManager; + @Mock + private MediaProjectionStopController.RoleHolderProvider mRoleManager; private AppOpsManager mAppOpsManager; @Mock @@ -145,7 +140,7 @@ public class MediaProjectionStopControllerTest { mContext.addMockSystemService(TelecomManager.class, mTelecomManager); mContext.setMockPackageManager(mPackageManager); - mStopController = new MediaProjectionStopController(mContext, mStopConsumer); + mStopController = new MediaProjectionStopController(mContext, mStopConsumer, mRoleManager); mService = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); @@ -170,8 +165,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testMediaProjectionNotRestricted() throws Exception { when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); @@ -180,8 +173,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testMediaProjectionRestricted() throws Exception { MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(); mediaProjection.notifyVirtualDisplayCreated(1); @@ -239,21 +230,13 @@ public class MediaProjectionStopControllerTest { @Test public void testExemptFromStoppingHasAppStreamingRole() throws Exception { - runWithRole( - AssociationRequest.DEVICE_PROFILE_APP_STREAMING, - () -> { - try { - MediaProjectionManagerService.MediaProjection mediaProjection = - createMediaProjection(); - doReturn(PackageManager.PERMISSION_DENIED).when( - mPackageManager).checkPermission( - RECORD_SENSITIVE_CONTENT, mediaProjection.packageName); - assertThat(mStopController.isExemptFromStopping(mediaProjection, - MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue(); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(); + doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( + RECORD_SENSITIVE_CONTENT, mediaProjection.packageName); + doReturn(List.of(mediaProjection.packageName)).when(mRoleManager).getRoleHoldersAsUser( + eq(AssociationRequest.DEVICE_PROFILE_APP_STREAMING), any(UserHandle.class)); + assertThat(mStopController.isExemptFromStopping(mediaProjection, + MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue(); } @Test @@ -316,8 +299,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testKeyguardLockedStateChanged_unlocked() { mStopController.onKeyguardLockedStateChanged(false); @@ -325,8 +306,6 @@ public class MediaProjectionStopControllerTest { } @Test - @EnableFlags( - android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testKeyguardLockedStateChanged_locked() { mStopController.onKeyguardLockedStateChanged(true); @@ -438,47 +417,4 @@ public class MediaProjectionStopControllerTest { MediaProjectionManager.TYPE_SCREEN_CAPTURE, false, mContext.getUser(), INVALID_DISPLAY); } - - /** - * Run the provided block giving the current context's package the provided role. - */ - @SuppressWarnings("SameParameterValue") - private void runWithRole(String role, Runnable block) { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - String packageName = mContext.getPackageName(); - UserHandle user = instrumentation.getTargetContext().getUser(); - RoleManager roleManager = Objects.requireNonNull( - mContext.getSystemService(RoleManager.class)); - try { - CountDownLatch latch = new CountDownLatch(1); - instrumentation.getUiAutomation().adoptShellPermissionIdentity( - Manifest.permission.MANAGE_ROLE_HOLDERS, - Manifest.permission.BYPASS_ROLE_QUALIFICATION); - - roleManager.setBypassingRoleQualification(true); - roleManager.addRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), success -> { - if (success) { - latch.countDown(); - } else { - Assert.fail("Couldn't set role for test (failure) " + role); - } - }); - assertWithMessage("Couldn't set role for test (timeout) : " + role) - .that(latch.await(1, TimeUnit.SECONDS)).isTrue(); - block.run(); - - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - roleManager.removeRoleHolderAsUser(role, packageName, - /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user, - mContext.getMainExecutor(), (aBool) -> { - }); - roleManager.setBypassingRoleQualification(false); - instrumentation.getUiAutomation() - .dropShellPermissionIdentity(); - } - } } diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt index 5862ac65eba9..af50effb7c8e 100644 --- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt @@ -92,6 +92,21 @@ class SupervisionServiceTest { simulateUserStarting(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(systemSupervisionPackage) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM) + fun onUserStarting_legacyProfileOwnerComponent_enablesSupervision() { + whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID)) + .thenReturn(supervisionProfileOwnerComponent) + + simulateUserStarting(USER_ID) + + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(supervisionProfileOwnerComponent.packageName) } @Test @@ -103,6 +118,7 @@ class SupervisionServiceTest { simulateUserStarting(USER_ID, preCreated = true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull() } @Test @@ -114,6 +130,7 @@ class SupervisionServiceTest { simulateUserStarting(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull() } @Test @@ -125,6 +142,21 @@ class SupervisionServiceTest { broadcastProfileOwnerChanged(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(systemSupervisionPackage) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM) + fun profileOwnerChanged_legacyProfileOwnerComponent_enablesSupervision() { + whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID)) + .thenReturn(supervisionProfileOwnerComponent) + + broadcastProfileOwnerChanged(USER_ID) + + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)) + .isEqualTo(supervisionProfileOwnerComponent.packageName) } @Test @@ -136,13 +168,14 @@ class SupervisionServiceTest { broadcastProfileOwnerChanged(USER_ID) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull() } @Test fun isActiveSupervisionApp_supervisionUid_supervisionEnabled_returnsTrue() { whenever(mockPackageManager.getPackagesForUid(APP_UID)) .thenReturn(arrayOf(systemSupervisionPackage)) - service.setSupervisionEnabledForUser(USER_ID, true) + service.mInternal.setSupervisionEnabledForUser(USER_ID, true) assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isTrue() } @@ -151,7 +184,7 @@ class SupervisionServiceTest { fun isActiveSupervisionApp_supervisionUid_supervisionNotEnabled_returnsFalse() { whenever(mockPackageManager.getPackagesForUid(APP_UID)) .thenReturn(arrayOf(systemSupervisionPackage)) - service.setSupervisionEnabledForUser(USER_ID, false) + service.mInternal.setSupervisionEnabledForUser(USER_ID, false) assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse() } @@ -167,15 +200,15 @@ class SupervisionServiceTest { fun setSupervisionEnabledForUser() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() - service.setSupervisionEnabledForUser(USER_ID, true) + service.mInternal.setSupervisionEnabledForUser(USER_ID, true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() - service.setSupervisionEnabledForUser(USER_ID, false) + service.mInternal.setSupervisionEnabledForUser(USER_ID, false) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() } @Test - fun supervisionEnabledForUser_internal() { + fun setSupervisionEnabledForUser_internal() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() service.mInternal.setSupervisionEnabledForUser(USER_ID, true) @@ -205,6 +238,13 @@ class SupervisionServiceTest { private val systemSupervisionPackage: String get() = context.getResources().getString(R.string.config_systemSupervision) + private val supervisionProfileOwnerComponent: ComponentName + get() = + context + .getResources() + .getString(R.string.config_defaultSupervisionProfileOwnerComponent) + .let(ComponentName::unflattenFromString)!! + private fun simulateUserStarting(userId: Int, preCreated: Boolean = false) { val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0) userInfo.preCreated = preCreated diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 5ac3e483231c..7af4ede05363 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4461,7 +4461,46 @@ public class SizeCompatTests extends WindowTestsBase { // are aligned to the top of the parentAppBounds assertEquals(new Rect(0, notchHeight, 1000, 1200), appBounds); assertEquals(new Rect(0, 0, 1000, 1200), bounds); + } + + @Test + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) + public void testInFreeform_boundsSandboxedToAppBounds() { + final int dw = 2800; + final int dh = 1400; + final int notchHeight = 100; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh) + .setNotch(notchHeight) + .build(); + setUpApp(display); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mTask.mDisplayContent.getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + Rect appBounds = new Rect(0, 0, 1000, 500); + Rect bounds = new Rect(0, 0, 1000, 600); + mTask.getWindowConfiguration().setAppBounds(appBounds); + mTask.getWindowConfiguration().setBounds(bounds); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + + // Bounds are sandboxed to appBounds in freeform. + assertDownScaled(); + assertEquals(mActivity.getWindowConfiguration().getAppBounds(), + mActivity.getWindowConfiguration().getBounds()); + + // Exit freeform. + mTask.mDisplayContent.getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mTask.getWindowConfiguration().setBounds(new Rect(0, 0, dw, dh)); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + assertFitted(); + appBounds = mActivity.getWindowConfiguration().getAppBounds(); + bounds = mActivity.getWindowConfiguration().getBounds(); + // Bounds are not sandboxed to appBounds. + assertNotEquals(appBounds, bounds); + assertEquals(notchHeight, appBounds.top - bounds.top); } @Test diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java index 9d9cac9702bb..d62fd63aeda6 100644 --- a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java @@ -22,8 +22,10 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.Rlog; import com.android.internal.telephony.flags.Flags; +import com.android.internal.telephony.util.TelephonyUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -220,7 +222,7 @@ public final class SatelliteSubscriberInfo implements Parcelable { StringBuilder sb = new StringBuilder(); sb.append("SubscriberId:"); - sb.append(mSubscriberId); + sb.append(Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mSubscriberId)); sb.append(","); sb.append("CarrierId:"); diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java index fb4f89ded547..75d3ec6d3fe6 100644 --- a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java @@ -21,8 +21,10 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.Rlog; import com.android.internal.telephony.flags.Flags; +import com.android.internal.telephony.util.TelephonyUtils; import java.util.Objects; @@ -132,7 +134,7 @@ public final class SatelliteSubscriberProvisionStatus implements Parcelable { StringBuilder sb = new StringBuilder(); sb.append("SatelliteSubscriberInfo:"); - sb.append(mSubscriberInfo); + sb.append(Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mSubscriberInfo)); sb.append(","); sb.append("ProvisionStatus:"); |