diff options
715 files changed, 10984 insertions, 4789 deletions
diff --git a/apct-tests/perftests/core/src/android/os/TracePerfTest.java b/apct-tests/perftests/core/src/android/os/TracePerfTest.java index 0b941c9d83c0..d9051240d399 100644 --- a/apct-tests/perftests/core/src/android/os/TracePerfTest.java +++ b/apct-tests/perftests/core/src/android/os/TracePerfTest.java @@ -127,14 +127,14 @@ public class TracePerfTest { public void testInstantPerfettoWithArgs() { PerfettoTrace.instant(FOO_CATEGORY, "testInstantP") .addArg("foo", "bar") - .addFlow(1) + .setFlow(1) .emit(); BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { PerfettoTrace.instant(FOO_CATEGORY, "testInstantP") .addArg("foo", "bar") - .addFlow(1) + .setFlow(1) .emit(); } } diff --git a/boot/boot-image-profile-extra.txt b/boot/boot-image-profile-extra.txt index ced0d176f174..ce99bfed1ce3 100644 --- a/boot/boot-image-profile-extra.txt +++ b/boot/boot-image-profile-extra.txt @@ -45,11 +45,15 @@ HSPLandroid/os/MessageQueue$OnFileDescriptorEventListener;->* HSPLandroid/os/MessageQueue$StackNodeType;->* HSPLandroid/os/MessageQueue$StateNode;->* HSPLandroid/os/MessageQueue$TimedParkStateNode;->* + +# For now, compile all methods in PerfettoTrace and PerfettoTrackEventExtra. +# Similar to the existing Trace APIs, these new APIs can impact the performance +# of many subsystems including MessageQueue. This also keeps benchmark +# comparisons between both APIs fair. HSPLandroid/os/PerfettoTrace$Category;->* HSPLandroid/os/PerfettoTrace;->* HSPLandroid/os/PerfettoTrackEventExtra;->* -HSPLandroid/os/PerfettoTrackEventExtra$BuilderImpl;->* -HSPLandroid/os/PerfettoTrackEventExtra$NoOpBuilder;->* +HSPLandroid/os/PerfettoTrackEventExtra$Builder;->* HSPLandroid/os/PerfettoTrackEventExtra$ArgBool;->* HSPLandroid/os/PerfettoTrackEventExtra$ArgInt64;->* HSPLandroid/os/PerfettoTrackEventExtra$ArgDouble;->* diff --git a/core/api/current.txt b/core/api/current.txt index 9ebb5068bf19..4862236a35e3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -53802,8 +53802,8 @@ package android.view { method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float); method public void setSecure(boolean); method public void setSurfaceLifecycle(int); - method public void setZOrderMediaOverlay(boolean); - method public void setZOrderOnTop(boolean); + method @Deprecated @FlaggedApi("android.view.flags.deprecate_surface_view_z_order_apis") public void setZOrderMediaOverlay(boolean); + method @Deprecated @FlaggedApi("android.view.flags.deprecate_surface_view_z_order_apis") public void setZOrderOnTop(boolean); field public static final int SURFACE_LIFECYCLE_DEFAULT = 0; // 0x0 field public static final int SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT = 2; // 0x2 field public static final int SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY = 1; // 0x1 diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 40069aa00106..526a213a6003 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -165,6 +165,16 @@ package android.hardware.usb { package android.media { + public class AudioDeviceVolumeManager { + method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.BLUETOOTH_STACK}) public void setDeviceAbsoluteMultiVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, @NonNull java.util.List<android.media.VolumeInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener); + method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.BLUETOOTH_STACK}) public void setDeviceAbsoluteVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, @NonNull android.media.VolumeInfo, @NonNull java.util.concurrent.Executor, @NonNull android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener); + } + + @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static interface AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener { + method public void onAudioDeviceVolumeAdjusted(@NonNull android.media.AudioDeviceAttributes, @NonNull android.media.VolumeInfo, int, int); + method public void onAudioDeviceVolumeChanged(@NonNull android.media.AudioDeviceAttributes, @NonNull android.media.VolumeInfo); + } + public class AudioManager { method public void adjustStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 89b377314887..4222c7c64672 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1737,6 +1737,7 @@ package android.hardware.display { method @NonNull public int[] getUserDisabledHdrTypes(); method public boolean isMinimalPostProcessingRequested(int); method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void overrideHdrTypes(int, @NonNull int[]); + method @FlaggedApi("com.android.server.display.feature.flags.delay_implicit_rr_registration_until_rr_accessed") public void resetImplicitRefreshRateCallbackStatus(); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode); method @RequiresPermission(android.Manifest.permission.MODIFY_HDR_CONVERSION_MODE) public void setHdrConversionMode(@NonNull android.hardware.display.HdrConversionMode); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 6151b8e2ef0a..a12c0674998e 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.PermissionMethod; import android.annotation.PermissionName; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.UserIdInt; import android.app.ActivityManager.ProcessCapability; import android.app.ActivityManager.RestrictionLevel; @@ -1365,8 +1366,8 @@ public abstract class ActivityManagerInternal { * watchdog reset. * @hide */ - public abstract void killApplicationSync(String pkgName, int appId, int userId, - String reason, int exitInfoReason); + public abstract void killApplicationSync(String pkgName, int appId, + @CanBeALL @UserIdInt int userId, String reason, int exitInfoReason); /** * Queries the offset data for a given method on a process. diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 4b1afa517122..01b2953362b5 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -146,6 +146,7 @@ interface IActivityTaskManager { int getFrontActivityScreenCompatMode(); void setFrontActivityScreenCompatMode(int mode); void setFocusedTask(int taskId); + boolean setTaskIsPerceptible(int taskId, boolean isPerceptible); boolean removeTask(int taskId); void removeAllVisibleRecentTasks(); List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, boolean filterOnlyVisibleRecents, diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index b611acf79bc3..eb9feb95bf3d 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -50,6 +50,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.ravenwood.annotation.RavenwoodKeep; import android.ravenwood.annotation.RavenwoodKeepPartialClass; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.ravenwood.annotation.RavenwoodReplace; import android.util.AndroidRuntimeException; import android.util.Log; @@ -460,6 +461,7 @@ public class Instrumentation { * * @param runner The code to run on the main thread. */ + @RavenwoodReplace(blockedBy = ActivityThread.class) public void runOnMainSync(Runnable runner) { validateNotAppThread(); SyncRunnable sr = new SyncRunnable(runner); @@ -467,6 +469,13 @@ public class Instrumentation { sr.waitForComplete(); } + private void runOnMainSync$ravenwood(Runnable runner) { + validateNotAppThread(); + SyncRunnable sr = new SyncRunnable(runner); + mInstrContext.getMainExecutor().execute(sr); + sr.waitForComplete(); + } + boolean isSdkSandboxAllowedToStartActivities() { return Process.isSdkSandbox() && mThread != null @@ -2442,7 +2451,8 @@ public class Instrumentation { } } - private final void validateNotAppThread() { + @RavenwoodKeep + private void validateNotAppThread() { if (Looper.myLooper() == Looper.getMainLooper()) { throw new RuntimeException( "This method can not be called from the main application thread"); @@ -2586,6 +2596,7 @@ public class Instrumentation { } } + @RavenwoodKeepWholeClass private static final class SyncRunnable implements Runnable { private final Runnable mTarget; private boolean mComplete; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 5dca1c70a2e6..719e4389d92d 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -18,6 +18,7 @@ package android.app; import static android.annotation.Dimension.DP; import static android.app.Flags.evenlyDividedCallStyleActionLayout; +import static android.app.Flags.notificationsRedesignTemplates; import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; @@ -818,7 +819,8 @@ public class Notification implements Parcelable R.layout.notification_2025_template_expanded_base, R.layout.notification_2025_template_heads_up_base, R.layout.notification_2025_template_header, - R.layout.notification_2025_template_conversation, + R.layout.notification_2025_template_collapsed_conversation, + R.layout.notification_2025_template_expanded_conversation, R.layout.notification_2025_template_collapsed_call, R.layout.notification_2025_template_expanded_call, R.layout.notification_2025_template_collapsed_messaging, @@ -5963,7 +5965,10 @@ public class Notification implements Parcelable || resId == getCompactHeadsUpBaseLayoutResource() || resId == getMessagingCompactHeadsUpLayoutResource() || resId == getCollapsedMessagingLayoutResource() - || resId == getCollapsedMediaLayoutResource()); + || resId == getCollapsedMediaLayoutResource() + || resId == getCollapsedConversationLayoutResource() + || (notificationsRedesignTemplates() + && resId == getCollapsedCallLayoutResource())); RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); resetStandardTemplate(contentView); @@ -6001,8 +6006,7 @@ public class Notification implements Parcelable // Update margins to leave space for the top line (but not for headerless views like // HUNS, which use a different layout that already accounts for that). Templates that // have content that will be displayed under the small icon also use a different margin. - if (Flags.notificationsRedesignTemplates() - && !p.mHeaderless && !p.mSkipTopLineAlignment) { + if (Flags.notificationsRedesignTemplates() && !p.mHeaderless) { int margin = getContentMarginTop(mContext, R.dimen.notification_2025_content_margin_top); contentView.setViewLayoutMargin(R.id.notification_main_column, @@ -7673,12 +7677,18 @@ public class Notification implements Parcelable } } + // Note: In the 2025 redesign, we use two separate layouts for the collapsed and expanded + // version of conversations. See below. private int getConversationLayoutResource() { - if (Flags.notificationsRedesignTemplates()) { - return R.layout.notification_2025_template_conversation; - } else { - return R.layout.notification_template_material_conversation; - } + return R.layout.notification_template_material_conversation; + } + + private int getCollapsedConversationLayoutResource() { + return R.layout.notification_2025_template_collapsed_conversation; + } + + private int getExpandedConversationLayoutResource() { + return R.layout.notification_2025_template_expanded_conversation; } private int getCollapsedCallLayoutResource() { @@ -9462,7 +9472,7 @@ public class Notification implements Parcelable boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL; boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; - boolean isHeaderless = !isConversationLayout && isCollapsed; + boolean isLegacyHeaderless = !isConversationLayout && isCollapsed; //TODO (b/217799515): ensure mConversationTitle always returns the correct // conversationTitle, probably set mConversationTitle = conversationTitle after this @@ -9483,7 +9493,8 @@ public class Notification implements Parcelable } else { isOneToOne = !isGroupConversation(); } - if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) { + if ((isLegacyHeaderless || notificationsRedesignTemplates()) + && isOneToOne && TextUtils.isEmpty(conversationTitle)) { conversationTitle = getOtherPersonName(); } @@ -9493,22 +9504,24 @@ public class Notification implements Parcelable .viewType(viewType) .highlightExpander(isConversationLayout) .hideProgress(true) - .title(isHeaderless ? conversationTitle : null) .text(null) .hideLeftIcon(isOneToOne) - .hideRightIcon(hideRightIcons || isOneToOne) - .headerTextSecondary(isHeaderless ? null : conversationTitle) - .skipTopLineAlignment(true); + .hideRightIcon(hideRightIcons || isOneToOne); + if (notificationsRedesignTemplates()) { + p.title(conversationTitle) + .hideAppName(isCollapsed); + } else { + p.title(isLegacyHeaderless ? conversationTitle : null) + .headerTextSecondary(isLegacyHeaderless ? null : conversationTitle); + } RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( - isConversationLayout - ? mBuilder.getConversationLayoutResource() - : isCollapsed - ? mBuilder.getCollapsedMessagingLayoutResource() - : mBuilder.getExpandedMessagingLayoutResource(), + getMessagingLayoutResource(isConversationLayout, isCollapsed), p, bindResult); - if (isConversationLayout) { + if (isConversationLayout && !notificationsRedesignTemplates()) { + // Redesign note: This view is replaced by the `title`, which is handled normally. mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p); + // Redesign note: This special divider is no longer needed. mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); } @@ -9538,7 +9551,18 @@ public class Notification implements Parcelable contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsImportantConversation", isImportantConversation); } - if (isHeaderless) { + if (notificationsRedesignTemplates() && !isCollapsed) { + // Align the title to the app/small icon in the expanded form. In other layouts, + // this margin is added directly to the notification_main_column parent, but for + // messages we don't want the margin to be applied to the actual messaging + // content since it can contain icons that are displayed below the app icon. + Resources res = mBuilder.mContext.getResources(); + int marginStart = res.getDimensionPixelSize( + R.dimen.notification_2025_content_margin_start); + contentView.setViewLayoutMargin(R.id.title, + RemoteViews.MARGIN_START, marginStart, TypedValue.COMPLEX_UNIT_PX); + } + if (isLegacyHeaderless) { // Collapsed legacy messaging style has a 1-line limit. contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); } @@ -9549,6 +9573,33 @@ public class Notification implements Parcelable return contentView; } + private int getMessagingLayoutResource(boolean isConversationLayout, boolean isCollapsed) { + if (notificationsRedesignTemplates()) { + // Note: We eventually would like to use the same layouts for both conversations and + // regular messaging notifications. + if (isConversationLayout) { + if (isCollapsed) { + return mBuilder.getCollapsedConversationLayoutResource(); + } else { + return mBuilder.getExpandedConversationLayoutResource(); + } + } else { + if (isCollapsed) { + return mBuilder.getCollapsedMessagingLayoutResource(); + } else { + return mBuilder.getExpandedMessagingLayoutResource(); + } + } + + } else { + return isConversationLayout + ? mBuilder.getConversationLayoutResource() + : isCollapsed + ? mBuilder.getCollapsedMessagingLayoutResource() + : mBuilder.getExpandedMessagingLayoutResource(); + } + } + private CharSequence getKey(Person person) { return person == null ? null : person.getKey() == null ? person.getName() : person.getKey(); @@ -10986,6 +11037,7 @@ public class Notification implements Parcelable private RemoteViews makeCallLayout(int viewType) { final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL; + final boolean isHeadsUp = viewType == StandardTemplateParams.VIEW_TYPE_HEADS_UP; Bundle extras = mBuilder.mN.extras; CharSequence title = mPerson != null ? mPerson.getName() : null; CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); @@ -11001,22 +11053,31 @@ public class Notification implements Parcelable .hideLeftIcon(true) .hideRightIcon(true) .hideAppName(isCollapsed) - .titleViewId(R.id.conversation_text) .title(title) - .text(text) - .summaryText(mBuilder.processLegacyText(mVerificationText)); + .text(text); + if (!notificationsRedesignTemplates()) { + // We're using the normal title in the redesign, not a special text. + p.titleViewId(R.id.conversation_text) + // The verification text is now part of the top line views, so this is no + // longer necessary. + .summaryText(mBuilder.processLegacyText(mVerificationText)); + } mBuilder.mActions = getActionsListWithSystemActions(); final RemoteViews contentView; if (isCollapsed) { contentView = mBuilder.applyStandardTemplate( mBuilder.getCollapsedCallLayoutResource(), p, null /* result */); + } else if (notificationsRedesignTemplates() && isHeadsUp) { + contentView = mBuilder.applyStandardTemplateWithActions( + mBuilder.getCollapsedCallLayoutResource(), p, null /* result */); } else { contentView = mBuilder.applyStandardTemplateWithActions( mBuilder.getExpandedCallLayoutResource(), p, null /* result */); } // Bind some extra conversation-specific header fields. - if (!p.mHideAppName) { + if (!notificationsRedesignTemplates() && !p.mHideAppName) { + // Redesign note: This special divider is no longer needed. mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE); } @@ -14676,7 +14737,6 @@ public class Notification implements Parcelable Icon mPromotedPicture; boolean mCallStyleActions; boolean mAllowTextWithProgress; - boolean mSkipTopLineAlignment; int mTitleViewId; int mTextViewId; @Nullable CharSequence mTitle; @@ -14702,7 +14762,6 @@ public class Notification implements Parcelable mPromotedPicture = null; mCallStyleActions = false; mAllowTextWithProgress = false; - mSkipTopLineAlignment = false; mTitleViewId = R.id.title; mTextViewId = R.id.text; mTitle = null; @@ -14769,11 +14828,6 @@ public class Notification implements Parcelable return this; } - public StandardTemplateParams skipTopLineAlignment(boolean skipTopLineAlignment) { - mSkipTopLineAlignment = skipTopLineAlignment; - return this; - } - final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) { this.mHideSnoozeButton = hideSnoozeButton; return this; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 08719fc549f8..5359ba44a3d2 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -14200,6 +14200,9 @@ public class DevicePolicyManager { * <li>Manifest.permission.ACTIVITY_RECOGNITION</li> * <li>Manifest.permission.BODY_SENSORS</li> * </ul> + * On devices running {@link android.os.Build.VERSION_CODES#BAKLAVA}, the + * {@link android.health.connect.HealthPermissions} are also included in the + * restricted list. * <p> * A profile owner may not grant these permissions (i.e. call this method with any of the * permissions listed above and {@code grantState} of {@code #PERMISSION_GRANT_STATE_GRANTED}), @@ -17644,9 +17647,17 @@ public class DevicePolicyManager { android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS }) public boolean isFinancedDevice() { - return isDeviceManaged() - && getDeviceOwnerType(getDeviceOwnerComponentOnAnyUser()) - == DEVICE_OWNER_TYPE_FINANCED; + try { + return isDeviceManaged() + && getDeviceOwnerType(getDeviceOwnerComponentOnAnyUser()) + == DEVICE_OWNER_TYPE_FINANCED; + } catch (IllegalStateException e) { + // getDeviceOwnerType() will throw IllegalStateException if the device does not have a + // DO. This can happen under a race condition when the DO is removed after + // isDeviceManaged() (so it still returns true) but before getDeviceOwnerType(). + // In this case, the device should not be considered a financed device. + return false; + } } // TODO(b/315298076): revert ag/25574027 and update the doc diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig index 94de03877fd7..709418393479 100644 --- a/core/java/android/app/supervision/flags.aconfig +++ b/core/java/android/app/supervision/flags.aconfig @@ -72,3 +72,11 @@ flag { description: "Flag that enables system APIs in Supervision Manager" bug: "382034839" } + +flag { + name: "enable_web_content_filters_screen" + is_exported: true + namespace: "supervision" + description: "Flag that enables the web content filters screen with Supervision settings entry point" + bug: "395134536" +} diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index efcaa0ea6f07..a753cbf956c6 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -23,6 +23,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SpecialUsers.CanBeALL; +import android.annotation.SpecialUsers.CanBeCURRENT; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -2709,7 +2711,7 @@ public abstract class ContentResolver implements ContentInterface { public final void registerContentObserverAsUser(@NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer, - @NonNull UserHandle userHandle) { + @NonNull @CanBeALL @CanBeCURRENT UserHandle userHandle) { Objects.requireNonNull(uri, "uri"); Objects.requireNonNull(observer, "observer"); Objects.requireNonNull(userHandle, "userHandle"); @@ -2723,7 +2725,7 @@ public abstract class ContentResolver implements ContentInterface { /** @hide - designated user version */ @UnsupportedAppUsage public final void registerContentObserver(Uri uri, boolean notifyForDescendants, - ContentObserver observer, @UserIdInt int userHandle) { + ContentObserver observer, @CanBeALL @CanBeCURRENT @UserIdInt int userHandle) { try { getContentService().registerContentObserver(uri, notifyForDescendants, observer.getContentObserver(), userHandle, mTargetSdkVersion); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 05596318aef5..2658efab0e44 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -482,7 +482,6 @@ public class Resources { * * @return Typeface The Typeface data associated with the resource. */ - @RavenwoodThrow(blockedBy = Typeface.class) @NonNull public Typeface getFont(@FontRes int id) throws NotFoundException { final TypedValue value = obtainTempTypedValue(); try { @@ -507,7 +506,6 @@ public class Resources { /** * @hide */ - @RavenwoodThrow(blockedBy = Typeface.class) public void preloadFonts(@ArrayRes int id) { final TypedArray array = obtainTypedArray(id); try { diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 3727a3468c1f..8c76fd70afd9 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -529,6 +529,7 @@ public class ResourcesImpl { for (int i = 0; i < locales.size(); i++) { selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag()); } + defaultLocale = adjustLanguageTag(lc.getDefaultLocale().toLanguageTag()); } else { selectedLocales = new String[]{ adjustLanguageTag(locales.get(0).toLanguageTag())}; @@ -1067,7 +1068,6 @@ public class ResourcesImpl { * Loads a font from XML or resources stream. */ @Nullable - @RavenwoodThrow(blockedBy = Typeface.class) public Typeface loadFont(Resources wrapper, TypedValue value, int id) { if (value.string == null) { throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 79185a10e156..ee7d008cf314 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -1043,7 +1043,6 @@ public class TypedArray implements AutoCloseable { * not a font resource. */ @Nullable - @RavenwoodThrow(blockedBy = Typeface.class) public Typeface getFont(@StyleableRes int index) { if (mRecycled) { throw new RuntimeException("Cannot make calls to a recycled instance!"); diff --git a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java index bfbcfd828114..1bf01f4beb1b 100644 --- a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java +++ b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java @@ -68,6 +68,7 @@ import java.util.Objects; * @hide */ @SystemApi +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class FontFamilyUpdateRequest { /** diff --git a/core/java/android/graphics/fonts/FontFileUpdateRequest.java b/core/java/android/graphics/fonts/FontFileUpdateRequest.java index cf1dca965216..1f2be6fa5050 100644 --- a/core/java/android/graphics/fonts/FontFileUpdateRequest.java +++ b/core/java/android/graphics/fonts/FontFileUpdateRequest.java @@ -28,6 +28,7 @@ import java.util.Objects; * @hide */ @SystemApi +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class FontFileUpdateRequest { private final ParcelFileDescriptor mParcelFileDescriptor; diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java index b7edef619a67..47ba51d98323 100644 --- a/core/java/android/hardware/contexthub/HubEndpoint.java +++ b/core/java/android/hardware/contexthub/HubEndpoint.java @@ -126,7 +126,16 @@ public class HubEndpoint { if (sessionExists) { Log.w( TAG, - "onSessionOpenComplete: session already exists, id=" + sessionId); + "onSessionOpenRequest: session already exists, id=" + sessionId); + } + + if (mLifecycleCallback == null) { + Log.w( + TAG, + "onSessionOpenRequest: " + + "failed to open session, no lifecycle callback attached", + new Exception()); + rejectSession(sessionId); } if (!sessionExists && mLifecycleCallback != null) { diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java index ca59be8fcc65..c5e2d7a1d9f9 100644 --- a/core/java/android/hardware/contexthub/HubEndpointSession.java +++ b/core/java/android/hardware/contexthub/HubEndpointSession.java @@ -88,7 +88,6 @@ public class HubEndpointSession implements AutoCloseable { : ContextHubTransaction.TYPE_HUB_MESSAGE_DEFAULT); if (!isResponseRequired) { // If the message doesn't require acknowledgement, respond with success immediately - // TODO(b/379162322): Improve handling of synchronous failures. mHubEndpoint.sendMessage(this, message, null); ret.setResponse( new ContextHubTransaction.Response<>( diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 7850e377ec4d..92a56fc575e3 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -851,6 +851,12 @@ public final class DisplayManager { * Registers a display listener to receive notifications about when * displays are added, removed or changed. * + * Because of the high frequency at which the refresh rate can change, clients will be + * registered for refresh rate change callbacks only when they request for refresh rate + * data({@link Display#getRefreshRate()}. Or alternately, you can consider using + * {@link #registerDisplayListener(Executor, long, DisplayListener)} and explicitly subscribe to + * {@link #EVENT_TYPE_DISPLAY_REFRESH_RATE} event + * * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)} * instead to subscribe for explicit events of interest * @@ -863,8 +869,8 @@ public final class DisplayManager { public void registerDisplayListener(DisplayListener listener, Handler handler) { registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED | EVENT_TYPE_DISPLAY_CHANGED - | EVENT_TYPE_DISPLAY_REFRESH_RATE - | EVENT_TYPE_DISPLAY_REMOVED); + | EVENT_TYPE_DISPLAY_REMOVED, 0, + ActivityThread.currentPackageName(), /* isEventFilterExplicit */ false); } /** @@ -882,9 +888,8 @@ public final class DisplayManager { */ public void registerDisplayListener(@NonNull DisplayListener listener, @Nullable Handler handler, @EventType long eventFilter) { - mGlobal.registerDisplayListener(listener, handler, - mGlobal.mapFiltersToInternalEventFlag(eventFilter, 0), - ActivityThread.currentPackageName()); + registerDisplayListener(listener, handler, eventFilter, 0, + ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true); } /** @@ -901,9 +906,8 @@ public final class DisplayManager { @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS) public void registerDisplayListener(@NonNull Executor executor, @EventType long eventFilter, @NonNull DisplayListener listener) { - mGlobal.registerDisplayListener(listener, executor, - mGlobal.mapFiltersToInternalEventFlag(eventFilter, 0), - ActivityThread.currentPackageName()); + registerDisplayListener(listener, executor, eventFilter, 0, + ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true); } /** @@ -924,9 +928,39 @@ public final class DisplayManager { public void registerDisplayListener(@NonNull DisplayListener listener, @Nullable Handler handler, @EventType long eventFilter, @PrivateEventType long privateEventFilter) { + registerDisplayListener(listener, handler, eventFilter, privateEventFilter, + ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true); + } + + /** + * Registers a display listener to receive notifications about given display event types. + * + * @param listener The listener to register. + * @param handler The handler on which the listener should be invoked, or null + * if the listener should be invoked on the calling thread's looper. + * @param eventFilter A bitmask of the event types for which this listener is subscribed. + * @param privateEventFilter A bitmask of the private event types for which this listener + * is subscribed. + * @param isEventFilterExplicit Indicates if the client explicitly supplied the display events + * to be subscribed to. + * + */ + private void registerDisplayListener(@NonNull DisplayListener listener, + @Nullable Handler handler, @EventType long eventFilter, + @PrivateEventType long privateEventFilter, String packageName, + boolean isEventFilterExplicit) { mGlobal.registerDisplayListener(listener, handler, mGlobal.mapFiltersToInternalEventFlag(eventFilter, privateEventFilter), - ActivityThread.currentPackageName()); + packageName, /* isEventFilterExplicit */ isEventFilterExplicit); + } + + private void registerDisplayListener(@NonNull DisplayListener listener, + Executor executor, @EventType long eventFilter, + @PrivateEventType long privateEventFilter, String packageName, + boolean isEventFilterExplicit) { + mGlobal.registerDisplayListener(listener, executor, + mGlobal.mapFiltersToInternalEventFlag(eventFilter, privateEventFilter), + packageName, /* isEventFilterExplicit */ isEventFilterExplicit); } /** @@ -1146,6 +1180,28 @@ public final class DisplayManager { } /** + * Resets the behavior that automatically registers clients for refresh rate change callbacks + * when they register via {@link #registerDisplayListener(DisplayListener, Handler)} + * + * <p>By default, clients are not registered for refresh rate change callbacks via + * {@link #registerDisplayListener(DisplayListener, Handler)}. However, calling + * {@link Display#getRefreshRate()} triggers automatic registration for existing and future + * {@link DisplayListener} instances. This method reverts this behavior, preventing new + * clients from being automatically registered for refresh rate change callbacks. Note that the + * existing ones will continue to stay registered + * + * <p>In essence, this method returns the system to its initial state, where explicit calls to + * {{@link Display#getRefreshRate()} are required to receive refresh rate change notifications. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED) + @TestApi + public void resetImplicitRefreshRateCallbackStatus() { + mGlobal.resetImplicitRefreshRateCallbackStatus(); + } + + /** * Overrides HDR modes for a display device. * * @hide diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index a7d610e54e2c..c4af87116eed 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -187,6 +187,9 @@ public final class DisplayManagerGlobal { private final Binder mToken = new Binder(); + // Guarded by mLock + private boolean mShouldImplicitlyRegisterRrChanges = false; + @VisibleForTesting public DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; @@ -390,27 +393,49 @@ public final class DisplayManagerGlobal { * the handler for the main thread. * If that is still null, a runtime exception will be thrown. * @param packageName of the calling package. + * @param isEventFilterExplicit Indicates if the client explicitly supplied the display events + * to be subscribed to. */ public void registerDisplayListener(@NonNull DisplayListener listener, @Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask, - String packageName) { + String packageName, boolean isEventFilterExplicit) { Looper looper = getLooperForHandler(handler); Handler springBoard = new Handler(looper); registerDisplayListener(listener, new HandlerExecutor(springBoard), internalEventFlagsMask, - packageName); + packageName, isEventFilterExplicit); } /** * Register a listener for display-related changes. * * @param listener The listener that will be called when display changes occur. + * @param handler Handler for the thread that will be receiving the callbacks. May be null. + * If null, listener will use the handler for the current thread, and if still null, + * the handler for the main thread. + * If that is still null, a runtime exception will be thrown. + * @param internalEventFlagsMask Mask of events to be listened to. + * @param packageName of the calling package. + */ + public void registerDisplayListener(@NonNull DisplayListener listener, + @Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask, + String packageName) { + registerDisplayListener(listener, handler, internalEventFlagsMask, packageName, true); + } + + + /** + * Register a listener for display-related changes. + * + * @param listener The listener that will be called when display changes occur. * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null. * @param internalEventFlagsMask Mask of events to be listened to. * @param packageName of the calling package. + * @param isEventFilterExplicit Indicates if the explicit events to be subscribed to + * were supplied or not */ public void registerDisplayListener(@NonNull DisplayListener listener, @NonNull Executor executor, @InternalEventFlag long internalEventFlagsMask, - String packageName) { + String packageName, boolean isEventFilterExplicit) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } @@ -429,7 +454,7 @@ public final class DisplayManagerGlobal { int index = findDisplayListenerLocked(listener); if (index < 0) { mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, - internalEventFlagsMask, packageName)); + internalEventFlagsMask, packageName, isEventFilterExplicit)); registerCallbackIfNeededLocked(); } else { mDisplayListeners.get(index).setEventsMask(internalEventFlagsMask); @@ -439,6 +464,22 @@ public final class DisplayManagerGlobal { } } + + /** + * Registers all the clients to INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE events if qualified + */ + public void registerForRefreshRateChanges() { + if (!Flags.delayImplicitRrRegistrationUntilRrAccessed()) { + return; + } + synchronized (mLock) { + if (!mShouldImplicitlyRegisterRrChanges) { + mShouldImplicitlyRegisterRrChanges = true; + updateCallbackIfNeededLocked(); + } + } + } + public void unregisterDisplayListener(DisplayListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); @@ -521,8 +562,14 @@ public final class DisplayManagerGlobal { long mask = 0; final int numListeners = mDisplayListeners.size(); for (int i = 0; i < numListeners; i++) { - mask |= mDisplayListeners.get(i).mInternalEventFlagsMask; + DisplayListenerDelegate displayListenerDelegate = mDisplayListeners.get(i); + if (!Flags.delayImplicitRrRegistrationUntilRrAccessed() + || mShouldImplicitlyRegisterRrChanges) { + displayListenerDelegate.implicitlyRegisterForRRChanges(); + } + mask |= displayListenerDelegate.mInternalEventFlagsMask; } + if (mDispatchNativeCallbacks) { mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED | INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED @@ -802,6 +849,18 @@ public final class DisplayManagerGlobal { } /** + * Resets the implicit registration of refresh rate change callbacks + * + */ + public void resetImplicitRefreshRateCallbackStatus() { + if (Flags.delayImplicitRrRegistrationUntilRrAccessed()) { + synchronized (mLock) { + mShouldImplicitlyRegisterRrChanges = false; + } + } + } + + /** * Overrides HDR modes for a display device. * */ @@ -1439,21 +1498,27 @@ public final class DisplayManagerGlobal { } } - private static final class DisplayListenerDelegate { + @VisibleForTesting + static final class DisplayListenerDelegate { public final DisplayListener mListener; public volatile long mInternalEventFlagsMask; + // Indicates if the client explicitly supplied the display events to be subscribed to. + private final boolean mIsEventFilterExplicit; + private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Executor mExecutor; private AtomicLong mGenerationId = new AtomicLong(1); private final String mPackageName; DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor, - @InternalEventFlag long internalEventFlag, String packageName) { + @InternalEventFlag long internalEventFlag, String packageName, + boolean isEventFilterExplicit) { mExecutor = executor; mListener = listener; mInternalEventFlagsMask = internalEventFlag; mPackageName = packageName; + mIsEventFilterExplicit = isEventFilterExplicit; } void sendDisplayEvent(int displayId, @DisplayEvent int event, @Nullable DisplayInfo info, @@ -1470,6 +1535,11 @@ public final class DisplayManagerGlobal { }); } + @VisibleForTesting + boolean isEventFilterExplicit() { + return mIsEventFilterExplicit; + } + void clearEvents() { mGenerationId.incrementAndGet(); } @@ -1478,6 +1548,17 @@ public final class DisplayManagerGlobal { mInternalEventFlagsMask = newInternalEventFlagsMask; } + private void implicitlyRegisterForRRChanges() { + // For backward compatibility, if the user didn't supply the explicit events while + // subscribing, register them to refresh rate change events if they subscribed to + // display changed events + if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) != 0 + && !mIsEventFilterExplicit) { + setEventsMask(mInternalEventFlagsMask + | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE); + } + } + private void handleDisplayEventInner(int displayId, @DisplayEvent int event, @Nullable DisplayInfo info, boolean forceUpdate) { if (extraLogging()) { @@ -1677,6 +1758,9 @@ public final class DisplayManagerGlobal { public void registerNativeChoreographerForRefreshRateCallbacks() { synchronized (mLock) { mDispatchNativeCallbacks = true; + if (Flags.delayImplicitRrRegistrationUntilRrAccessed()) { + mShouldImplicitlyRegisterRrChanges = true; + } registerCallbackIfNeededLocked(); updateCallbackIfNeededLocked(); DisplayInfo display = getDisplayInfoLocked(Display.DEFAULT_DISPLAY); @@ -1806,4 +1890,9 @@ public final class DisplayManagerGlobal { return baseEventMask; } + + @VisibleForTesting + CopyOnWriteArrayList<DisplayListenerDelegate> getDisplayListeners() { + return mDisplayListeners; + } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index a528ba4b16bf..7b47efd47008 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -990,6 +990,8 @@ public class InputMethodService extends AbstractInputMethodService { } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (android.view.inputmethod.Flags.refactorInsetsController()) { + // After the IME window was hidden, we can remove its surface + scheduleImeSurfaceRemoval(); // The hide request first finishes the animation and then proceeds to the server // side, finally reaching here, marking this the end state. ImeTracker.forLogging().onHidden(statsToken); diff --git a/core/java/android/os/BundleMerger.java b/core/java/android/os/BundleMerger.java index dc243a5e9c08..fd5a18c22a37 100644 --- a/core/java/android/os/BundleMerger.java +++ b/core/java/android/os/BundleMerger.java @@ -119,11 +119,23 @@ public class BundleMerger implements Parcelable { public static final int STRATEGY_ARRAY_APPEND = 50; /** + * Merge strategy that combines two conflicting array values by creating a new array + * containing all unique elements from both arrays. + */ + public static final int STRATEGY_ARRAY_UNION = 55; + + /** * Merge strategy that combines two conflicting {@link ArrayList} values by * appending the last {@link ArrayList} after the first {@link ArrayList}. */ public static final int STRATEGY_ARRAY_LIST_APPEND = 60; + /** + * Merge strategy that combines two conflicting {@link String} values by + * appending the last {@link String} after the first {@link String}. + */ + public static final int STRATEGY_STRING_APPEND = 70; + @IntDef(flag = false, prefix = { "STRATEGY_" }, value = { STRATEGY_REJECT, STRATEGY_FIRST, @@ -136,7 +148,9 @@ public class BundleMerger implements Parcelable { STRATEGY_BOOLEAN_AND, STRATEGY_BOOLEAN_OR, STRATEGY_ARRAY_APPEND, + STRATEGY_ARRAY_UNION, STRATEGY_ARRAY_LIST_APPEND, + STRATEGY_STRING_APPEND, }) @Retention(RetentionPolicy.SOURCE) public @interface Strategy {} @@ -298,8 +312,12 @@ public class BundleMerger implements Parcelable { return booleanOr(first, last); case STRATEGY_ARRAY_APPEND: return arrayAppend(first, last); + case STRATEGY_ARRAY_UNION: + return arrayUnion(first, last); case STRATEGY_ARRAY_LIST_APPEND: return arrayListAppend(first, last); + case STRATEGY_STRING_APPEND: + return stringAppend(first, last); default: throw new UnsupportedOperationException(); } @@ -361,6 +379,29 @@ public class BundleMerger implements Parcelable { return res; } + private static @NonNull Object arrayUnion(@NonNull Object first, @NonNull Object last) { + if (!first.getClass().isArray()) { + throw new IllegalArgumentException("Unable to union " + first.getClass()); + } + final int firstLength = Array.getLength(first); + final int lastLength = Array.getLength(last); + final ArrayList<Object> list = new ArrayList<>(firstLength + lastLength); + final ArraySet<Object> set = new ArraySet<>(); + for (int i = 0; i < firstLength; i++) { + set.add(Array.get(first, i)); + } + for (int i = 0; i < lastLength; i++) { + set.add(Array.get(last, i)); + } + final Class<?> clazz = first.getClass().getComponentType(); + final int setSize = set.size(); + final Object res = Array.newInstance(clazz, setSize); + for (int i = 0; i < setSize; i++) { + Array.set(res, i, set.valueAt(i)); + } + return res; + } + @SuppressWarnings("unchecked") private static @NonNull Object arrayListAppend(@NonNull Object first, @NonNull Object last) { if (!(first instanceof ArrayList)) { @@ -374,6 +415,13 @@ public class BundleMerger implements Parcelable { return res; } + private static @NonNull Object stringAppend(@NonNull Object first, @NonNull Object last) { + if (!(first instanceof String)) { + throw new IllegalArgumentException("Unable to append " + first.getClass()); + } + return ((String) first) + ((String) last); + } + public static final @android.annotation.NonNull Parcelable.Creator<BundleMerger> CREATOR = new Parcelable.Creator<BundleMerger>() { @Override diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java index 741d542ecb3b..932836f8a050 100644 --- a/core/java/android/os/PerfettoTrace.java +++ b/core/java/android/os/PerfettoTrace.java @@ -232,10 +232,6 @@ public final class PerfettoTrace { * @param eventName The event name to appear in the trace. */ public static PerfettoTrackEventExtra.Builder instant(Category category, String eventName) { - if (!category.isEnabled()) { - return PerfettoTrackEventExtra.noOpBuilder(); - } - return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_INSTANT, category) .setEventName(eventName); } @@ -247,10 +243,6 @@ public final class PerfettoTrace { * @param eventName The event name to appear in the trace. */ public static PerfettoTrackEventExtra.Builder begin(Category category, String eventName) { - if (!category.isEnabled()) { - return PerfettoTrackEventExtra.noOpBuilder(); - } - return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_BEGIN, category) .setEventName(eventName); } @@ -261,10 +253,6 @@ public final class PerfettoTrace { * @param category The perfetto category. */ public static PerfettoTrackEventExtra.Builder end(Category category) { - if (!category.isEnabled()) { - return PerfettoTrackEventExtra.noOpBuilder(); - } - return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_END, category); } @@ -275,10 +263,6 @@ public final class PerfettoTrace { * @param value The value of the counter. */ public static PerfettoTrackEventExtra.Builder counter(Category category, long value) { - if (!category.isEnabled()) { - return PerfettoTrackEventExtra.noOpBuilder(); - } - return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category) .setCounter(value); } @@ -302,10 +286,6 @@ public final class PerfettoTrace { * @param value The value of the counter. */ public static PerfettoTrackEventExtra.Builder counter(Category category, double value) { - if (!category.isEnabled()) { - return PerfettoTrackEventExtra.noOpBuilder(); - } - return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category) .setCounter(value); } diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java index 68442293c3a3..f4b5dfe76f88 100644 --- a/core/java/android/os/PerfettoTrackEventExtra.java +++ b/core/java/android/os/PerfettoTrackEventExtra.java @@ -35,8 +35,8 @@ import java.util.function.Supplier; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public final class PerfettoTrackEventExtra { + private static final boolean DEBUG = false; private static final int DEFAULT_EXTRA_CACHE_SIZE = 5; - private static final Builder NO_OP_BUILDER = new NoOpBuilder(); private static final ThreadLocal<PerfettoTrackEventExtra> sTrackEventExtra = new ThreadLocal<PerfettoTrackEventExtra>() { @Override @@ -46,7 +46,7 @@ public final class PerfettoTrackEventExtra { }; private static final AtomicLong sNamedTrackId = new AtomicLong(); private static final Supplier<Flow> sFlowSupplier = Flow::new; - private static final Supplier<BuilderImpl> sBuilderSupplier = BuilderImpl::new; + private static final Supplier<Builder> sBuilderSupplier = Builder::new; private static final Supplier<FieldInt64> sFieldInt64Supplier = FieldInt64::new; private static final Supplier<FieldDouble> sFieldDoubleSupplier = FieldDouble::new; private static final Supplier<FieldString> sFieldStringSupplier = FieldString::new; @@ -56,6 +56,8 @@ public final class PerfettoTrackEventExtra { private CounterInt64 mCounterInt64; private CounterDouble mCounterDouble; private Proto mProto; + private Flow mFlow; + private Flow mTerminatingFlow; /** * Represents a native pointer to a Perfetto C SDK struct. E.g. PerfettoTeHlExtra. @@ -135,245 +137,10 @@ public final class PerfettoTrackEventExtra { } } - public interface Builder { - /** - * Emits the track event. - */ - void emit(); - - /** - * Initialize the builder for a new trace event. - */ - Builder init(int traceType, PerfettoTrace.Category category); - - /** - * Sets the event name for the track event. - */ - Builder setEventName(String eventName); - - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ - Builder addArg(String name, long val); - - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ - Builder addArg(String name, boolean val); - - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ - Builder addArg(String name, double val); - - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ - Builder addArg(String name, String val); - - /** - * Adds a flow with {@code id}. - */ - Builder addFlow(int id); - - /** - * Adds a terminating flow with {@code id}. - */ - Builder addTerminatingFlow(int id); - - /** - * Adds the events to a named track instead of the thread track where the - * event occurred. - */ - Builder usingNamedTrack(long parentUuid, String name); - - /** - * Adds the events to a process scoped named track instead of the thread track where the - * event occurred. - */ - Builder usingProcessNamedTrack(String name); - - /** - * Adds the events to a thread scoped named track instead of the thread track where the - * event occurred. - */ - Builder usingThreadNamedTrack(long tid, String name); - - /** - * Adds the events to a counter track instead. This is required for - * setting counter values. - */ - Builder usingCounterTrack(long parentUuid, String name); - - /** - * Adds the events to a process scoped counter track instead. This is required for - * setting counter values. - */ - Builder usingProcessCounterTrack(String name); - - /** - * Adds the events to a thread scoped counter track instead. This is required for - * setting counter values. - */ - Builder usingThreadCounterTrack(long tid, String name); - - /** - * Sets a long counter value on the event. - * - */ - Builder setCounter(long val); - - /** - * Sets a double counter value on the event. - * - */ - Builder setCounter(double val); - - /** - * Adds a proto field with field id {@code id} and value {@code val}. - */ - Builder addField(long id, long val); - - /** - * Adds a proto field with field id {@code id} and value {@code val}. - */ - Builder addField(long id, double val); - - /** - * Adds a proto field with field id {@code id} and value {@code val}. - */ - Builder addField(long id, String val); - - /** - * Begins a proto field with field - * Fields can be added from this point and there must be a corresponding - * {@link endProto}. - * - * The proto field is a singleton and all proto fields get added inside the - * one {@link beginProto} and {@link endProto} within the {@link Builder}. - */ - Builder beginProto(); - - /** - * Ends a proto field. - */ - Builder endProto(); - - /** - * Begins a nested proto field with field id {@code id}. - * Fields can be added from this point and there must be a corresponding - * {@link endNested}. - */ - Builder beginNested(long id); - - /** - * Ends a nested proto field. - */ - Builder endNested(); - } - - @android.ravenwood.annotation.RavenwoodKeepWholeClass - public static final class NoOpBuilder implements Builder { - @Override - public void emit() {} - @Override - public Builder init(int traceType, PerfettoTrace.Category category) { - return this; - } - @Override - public Builder setEventName(String eventName) { - return this; - } - @Override - public Builder addArg(String name, long val) { - return this; - } - @Override - public Builder addArg(String name, boolean val) { - return this; - } - @Override - public Builder addArg(String name, double val) { - return this; - } - @Override - public Builder addArg(String name, String val) { - return this; - } - @Override - public Builder addFlow(int id) { - return this; - } - @Override - public Builder addTerminatingFlow(int id) { - return this; - } - @Override - public Builder usingNamedTrack(long parentUuid, String name) { - return this; - } - @Override - public Builder usingProcessNamedTrack(String name) { - return this; - } - @Override - public Builder usingThreadNamedTrack(long tid, String name) { - return this; - } - @Override - public Builder usingCounterTrack(long parentUuid, String name) { - return this; - } - @Override - public Builder usingProcessCounterTrack(String name) { - return this; - } - @Override - public Builder usingThreadCounterTrack(long tid, String name) { - return this; - } - @Override - public Builder setCounter(long val) { - return this; - } - @Override - public Builder setCounter(double val) { - return this; - } - @Override - public Builder addField(long id, long val) { - return this; - } - @Override - public Builder addField(long id, double val) { - return this; - } - @Override - public Builder addField(long id, String val) { - return this; - } - @Override - public Builder beginProto() { - return this; - } - @Override - public Builder endProto() { - return this; - } - @Override - public Builder beginNested(long id) { - return this; - } - @Override - public Builder endNested() { - return this; - } - } - /** * Builder for Perfetto track event extras. */ - public static final class BuilderImpl implements Builder { + public static final class Builder { // For performance reasons, we hold a reference to mExtra as a holder for // perfetto pointers being added. This way, we avoid an additional list to hold // the pointers in Java and we can pass them down directly to native code. @@ -386,10 +153,13 @@ public final class PerfettoTrackEventExtra { private Builder mParent; private FieldContainer mCurrentContainer; + private boolean mIsCategoryEnabled; private final CounterInt64 mCounterInt64; private final CounterDouble mCounterDouble; private final Proto mProto; + private final Flow mFlow; + private final Flow mTerminatingFlow; private final RingBuffer<NamedTrack> mNamedTrackCache; private final RingBuffer<CounterTrack> mCounterTrackCache; @@ -403,9 +173,9 @@ public final class PerfettoTrackEventExtra { private final Pool<FieldString> mFieldStringCache; private final Pool<FieldNested> mFieldNestedCache; private final Pool<Flow> mFlowCache; - private final Pool<BuilderImpl> mBuilderCache; + private final Pool<Builder> mBuilderCache; - private BuilderImpl() { + private Builder() { mExtra = sTrackEventExtra.get(); mNamedTrackCache = mExtra.mNamedTrackCache; mCounterTrackCache = mExtra.mCounterTrackCache; @@ -423,20 +193,32 @@ public final class PerfettoTrackEventExtra { mCounterInt64 = mExtra.getCounterInt64(); mCounterDouble = mExtra.getCounterDouble(); mProto = mExtra.getProto(); + mFlow = mExtra.getFlow(); + mTerminatingFlow = mExtra.getTerminatingFlow(); } - @Override + /** + * Emits the track event. + */ public void emit() { - checkParent(); - mIsBuilt = true; + if (!mIsCategoryEnabled) { + return; + } + if (DEBUG) { + checkParent(); + } + mIsBuilt = true; native_emit(mTraceType, mCategory.getPtr(), mEventName, mExtra.getPtr()); - // Reset after emitting to free any the extras used to trace the event. - mExtra.reset(); } - @Override + /** + * Initialize the builder for a new trace event. + */ public Builder init(int traceType, PerfettoTrace.Category category) { + if (!category.isEnabled()) { + return this; + } mTraceType = traceType; mCategory = category; mEventName = ""; @@ -449,18 +231,27 @@ public final class PerfettoTrackEventExtra { mExtra.reset(); // Reset after on init in case the thread created builders without calling emit - return initInternal(this, null); + return initInternal(this, null, true); } - @Override + /** + * Sets the event name for the track event. + */ public Builder setEventName(String eventName) { mEventName = eventName; return this; } - @Override + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ public Builder addArg(String name, long val) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } ArgInt64 arg = mArgInt64Cache.get(name.hashCode()); if (arg == null || !arg.getName().equals(name)) { arg = new ArgInt64(name); @@ -471,9 +262,16 @@ public final class PerfettoTrackEventExtra { return this; } - @Override + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ public Builder addArg(String name, boolean val) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } ArgBool arg = mArgBoolCache.get(name.hashCode()); if (arg == null || !arg.getName().equals(name)) { arg = new ArgBool(name); @@ -484,9 +282,16 @@ public final class PerfettoTrackEventExtra { return this; } - @Override + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ public Builder addArg(String name, double val) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } ArgDouble arg = mArgDoubleCache.get(name.hashCode()); if (arg == null || !arg.getName().equals(name)) { arg = new ArgDouble(name); @@ -497,9 +302,16 @@ public final class PerfettoTrackEventExtra { return this; } - @Override + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ public Builder addArg(String name, String val) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } ArgString arg = mArgStringCache.get(name.hashCode()); if (arg == null || !arg.getName().equals(name)) { arg = new ArgString(name); @@ -510,27 +322,79 @@ public final class PerfettoTrackEventExtra { return this; } - @Override + /** + * Adds a flow with {@code id}. + */ public Builder addFlow(int id) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } Flow flow = mFlowCache.get(sFlowSupplier); flow.setProcessFlow(id); mExtra.addPerfettoPointer(flow); return this; } - @Override + /** + * Adds a terminating flow with {@code id}. + */ public Builder addTerminatingFlow(int id) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } Flow flow = mFlowCache.get(sFlowSupplier); flow.setProcessTerminatingFlow(id); mExtra.addPerfettoPointer(flow); return this; } - @Override + /** + * Adds a flow with {@code id}. + */ + public Builder setFlow(int id) { + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } + mFlow.setProcessFlow(id); + mExtra.addPerfettoPointer(mFlow); + return this; + } + + /** + * Adds a terminating flow with {@code id}. + */ + public Builder setTerminatingFlow(int id) { + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } + mTerminatingFlow.setProcessTerminatingFlow(id); + mExtra.addPerfettoPointer(mTerminatingFlow); + return this; + } + + /** + * Adds the events to a named track instead of the thread track where the + * event occurred. + */ public Builder usingNamedTrack(long parentUuid, String name) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } NamedTrack track = mNamedTrackCache.get(name.hashCode()); if (track == null || !track.getName().equals(name)) { @@ -541,19 +405,39 @@ public final class PerfettoTrackEventExtra { return this; } - @Override + /** + * Adds the events to a process scoped named track instead of the thread track where the + * event occurred. + */ public Builder usingProcessNamedTrack(String name) { + if (!mIsCategoryEnabled) { + return this; + } return usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), name); } - @Override + /** + * Adds the events to a thread scoped named track instead of the thread track where the + * event occurred. + */ public Builder usingThreadNamedTrack(long tid, String name) { + if (!mIsCategoryEnabled) { + return this; + } return usingNamedTrack(PerfettoTrace.getThreadTrackUuid(tid), name); } - @Override + /** + * Adds the events to a counter track instead. This is required for + * setting counter values. + */ public Builder usingCounterTrack(long parentUuid, String name) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } CounterTrack track = mCounterTrackCache.get(name.hashCode()); if (track == null || !track.getName().equals(name)) { @@ -564,95 +448,178 @@ public final class PerfettoTrackEventExtra { return this; } - @Override + /** + * Adds the events to a process scoped counter track instead. This is required for + * setting counter values. + */ public Builder usingProcessCounterTrack(String name) { + if (!mIsCategoryEnabled) { + return this; + } return usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), name); } - @Override + /** + * Adds the events to a thread scoped counter track instead. This is required for + * setting counter values. + */ public Builder usingThreadCounterTrack(long tid, String name) { + if (!mIsCategoryEnabled) { + return this; + } return usingCounterTrack(PerfettoTrace.getThreadTrackUuid(tid), name); } - @Override + /** + * Sets a long counter value on the event. + * + */ public Builder setCounter(long val) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } mCounterInt64.setValue(val); mExtra.addPerfettoPointer(mCounterInt64); return this; } - @Override + /** + * Sets a double counter value on the event. + * + */ public Builder setCounter(double val) { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } mCounterDouble.setValue(val); mExtra.addPerfettoPointer(mCounterDouble); return this; } - @Override + /** + * Adds a proto field with field id {@code id} and value {@code val}. + */ public Builder addField(long id, long val) { - checkContainer(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkContainer(); + } FieldInt64 field = mFieldInt64Cache.get(sFieldInt64Supplier); field.setValue(id, val); mExtra.addPerfettoPointer(mCurrentContainer, field); return this; } - @Override + /** + * Adds a proto field with field id {@code id} and value {@code val}. + */ public Builder addField(long id, double val) { - checkContainer(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkContainer(); + } FieldDouble field = mFieldDoubleCache.get(sFieldDoubleSupplier); field.setValue(id, val); mExtra.addPerfettoPointer(mCurrentContainer, field); return this; } - @Override + /** + * Adds a proto field with field id {@code id} and value {@code val}. + */ public Builder addField(long id, String val) { - checkContainer(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkContainer(); + } FieldString field = mFieldStringCache.get(sFieldStringSupplier); field.setValue(id, val); mExtra.addPerfettoPointer(mCurrentContainer, field); return this; } - @Override + /** + * Begins a proto field. + * Fields can be added from this point and there must be a corresponding + * {@link endProto}. + * + * The proto field is a singleton and all proto fields get added inside the + * one {@link beginProto} and {@link endProto} within the {@link Builder}. + */ public Builder beginProto() { - checkParent(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkParent(); + } mProto.clearFields(); mExtra.addPerfettoPointer(mProto); - return mBuilderCache.get(sBuilderSupplier).initInternal(this, mProto); + return mBuilderCache.get(sBuilderSupplier).initInternal(this, mProto, true); } - @Override + /** + * Ends a proto field. + */ public Builder endProto() { + if (!mIsCategoryEnabled) { + return this; + } if (mParent == null || mCurrentContainer == null) { throw new IllegalStateException("No proto to end"); } return mParent; } - @Override + /** + * Begins a nested proto field with field id {@code id}. + * Fields can be added from this point and there must be a corresponding + * {@link endNested}. + */ public Builder beginNested(long id) { - checkContainer(); + if (!mIsCategoryEnabled) { + return this; + } + if (DEBUG) { + checkContainer(); + } FieldNested field = mFieldNestedCache.get(sFieldNestedSupplier); field.setId(id); mExtra.addPerfettoPointer(mCurrentContainer, field); - return mBuilderCache.get(sBuilderSupplier).initInternal(this, field); + return mBuilderCache.get(sBuilderSupplier).initInternal(this, field, true); } - @Override + /** + * Ends a nested proto field. + */ public Builder endNested() { + if (!mIsCategoryEnabled) { + return this; + } if (mParent == null || mCurrentContainer == null) { throw new IllegalStateException("No nested field to end"); } return mParent; } - private Builder initInternal(Builder parent, FieldContainer container) { + + private Builder initInternal(Builder parent, FieldContainer field, + boolean isCategoryEnabled) { mParent = parent; - mCurrentContainer = container; + mCurrentContainer = field; + mIsCategoryEnabled = isCategoryEnabled; mIsBuilt = false; return this; @@ -685,14 +652,8 @@ public final class PerfettoTrackEventExtra { * Start a {@link Builder} to build a {@link PerfettoTrackEventExtra}. */ public static Builder builder() { - return sTrackEventExtra.get().mBuilderCache.get(sBuilderSupplier).initInternal(null, null); - } - - /** - * Returns a no-op {@link Builder}. Useful if a category is disabled. - */ - public static Builder noOpBuilder() { - return NO_OP_BUILDER; + return sTrackEventExtra.get().mBuilderCache.get(sBuilderSupplier).initInternal(null, null, + false); } private final RingBuffer<NamedTrack> mNamedTrackCache = @@ -710,7 +671,7 @@ public final class PerfettoTrackEventExtra { private final Pool<FieldString> mFieldStringCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); private final Pool<FieldNested> mFieldNestedCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); private final Pool<Flow> mFlowCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); - private final Pool<BuilderImpl> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); + private final Pool<Builder> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); private static final NativeAllocationRegistry sRegistry = NativeAllocationRegistry.createMalloced( @@ -757,6 +718,7 @@ public final class PerfettoTrackEventExtra { mPendingPointers.clear(); } + @android.ravenwood.annotation.RavenwoodReplace private CounterInt64 getCounterInt64() { if (mCounterInt64 == null) { mCounterInt64 = new CounterInt64(); @@ -764,6 +726,7 @@ public final class PerfettoTrackEventExtra { return mCounterInt64; } + @android.ravenwood.annotation.RavenwoodReplace private CounterDouble getCounterDouble() { if (mCounterDouble == null) { mCounterDouble = new CounterDouble(); @@ -771,6 +734,7 @@ public final class PerfettoTrackEventExtra { return mCounterDouble; } + @android.ravenwood.annotation.RavenwoodReplace private Proto getProto() { if (mProto == null) { mProto = new Proto(); @@ -778,6 +742,22 @@ public final class PerfettoTrackEventExtra { return mProto; } + @android.ravenwood.annotation.RavenwoodReplace + private Flow getFlow() { + if (mFlow == null) { + mFlow = new Flow(); + } + return mFlow; + } + + @android.ravenwood.annotation.RavenwoodReplace + private Flow getTerminatingFlow() { + if (mTerminatingFlow == null) { + mTerminatingFlow = new Flow(); + } + return mTerminatingFlow; + } + private static final class Flow implements PerfettoPointer { private static final NativeAllocationRegistry sRegistry = NativeAllocationRegistry.createMalloced( @@ -1337,4 +1317,29 @@ public final class PerfettoTrackEventExtra { // Tracing currently completely disabled under Ravenwood return 0; } + + private CounterInt64 getCounterInt64$ravenwood() { + // Tracing currently completely disabled under Ravenwood + return null; + } + + private CounterDouble getCounterDouble$ravenwood() { + // Tracing currently completely disabled under Ravenwood + return null; + } + + private Proto getProto$ravenwood() { + // Tracing currently completely disabled under Ravenwood + return null; + } + + private Flow getFlow$ravenwood() { + // Tracing currently completely disabled under Ravenwood + return null; + } + + private Flow getTerminatingFlow$ravenwood() { + // Tracing currently completely disabled under Ravenwood + return null; + } } diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 7ceb948945fd..0615578935a1 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -145,3 +145,13 @@ flag { purpose: PURPOSE_FEATURE } } + +flag { + namespace: "haptics" + name: "fix_vibration_thread_callback_handling" + description: "Fix how the VibrationThread handles late callbacks from the vibrator HAL" + bug: "395005081" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index d469a2f985fa..ca24c0c6c376 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -514,3 +514,12 @@ flag { description: "Force AttributionSource.myAttributionSource() to return a default device id" bug: "343121936" } + +flag { + name: "grant_read_blocked_numbers_to_system_ui_intelligence" + is_exported: true + is_fixed_read_only: true + namespace: "permissions" + description: "This flag is used to add role protection to READ_BLOCKED_NUMBERS for SYSTEM_UI_INTELLIGENCE" + bug: "354758615" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 4ebfe53cab58..538283e10738 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13017,18 +13017,16 @@ public final class Settings { * false/0. * @hide */ - @Readable public static final String REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI = "redact_otp_on_wifi"; /** - * Toggle for whether to immediately redact OTP notifications, or require the device to be - * locked for 10 minutes. Defaults to false/0 + * Time (in milliseconds) that the device should need to be locked, in order for an OTP + * notification to be redacted. Default is 10 minutes (600,000 ms) * @hide */ - @Readable - public static final String REDACT_OTP_NOTIFICATION_IMMEDIATELY = - "remove_otp_redaction_delay"; + public static final String OTP_NOTIFICATION_REDACTION_LOCK_TIME = + "otp_redaction_lock_time"; /** * These entries are considered common between the personal and the managed profile, diff --git a/core/java/android/text/AlteredCharSequence.java b/core/java/android/text/AlteredCharSequence.java index 971a47dba6e8..a05c690a9e30 100644 --- a/core/java/android/text/AlteredCharSequence.java +++ b/core/java/android/text/AlteredCharSequence.java @@ -24,6 +24,7 @@ package android.text; * @deprecated The functionality this class offers is easily implemented outside the framework. */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AlteredCharSequence implements CharSequence, GetChars { diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java index 31da79995172..fcdd50abc02a 100644 --- a/core/java/android/text/AndroidBidi.java +++ b/core/java/android/text/AndroidBidi.java @@ -28,6 +28,7 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AndroidBidi { /** diff --git a/core/java/android/text/AndroidCharacter.java b/core/java/android/text/AndroidCharacter.java index c5f1a01f5927..37c4fdbef915 100644 --- a/core/java/android/text/AndroidCharacter.java +++ b/core/java/android/text/AndroidCharacter.java @@ -22,6 +22,7 @@ package android.text; * @deprecated Use various methods from {@link android.icu.lang.UCharacter}, instead. */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AndroidCharacter { public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0; diff --git a/core/java/android/text/Annotation.java b/core/java/android/text/Annotation.java index bb5d3ea7da4b..ac3e5a964a33 100644 --- a/core/java/android/text/Annotation.java +++ b/core/java/android/text/Annotation.java @@ -23,6 +23,7 @@ import android.os.Parcel; * TextView save/restore cycles and can be used to keep application-specific * data that needs to be maintained for regions of text. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Annotation implements ParcelableSpan { private final String mKey; private final String mValue; diff --git a/core/java/android/text/AutoGrowArray.java b/core/java/android/text/AutoGrowArray.java index e428377a0a31..06c74c3370c1 100644 --- a/core/java/android/text/AutoGrowArray.java +++ b/core/java/android/text/AutoGrowArray.java @@ -30,6 +30,7 @@ import libcore.util.EmptyArray; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class AutoGrowArray { private static final int MIN_CAPACITY_INCREMENT = 12; private static final int MAX_CAPACITY_TO_BE_KEPT = 10000; diff --git a/core/java/android/text/AutoText.java b/core/java/android/text/AutoText.java index c5339a42cbd1..d7b0547e6c4c 100644 --- a/core/java/android/text/AutoText.java +++ b/core/java/android/text/AutoText.java @@ -31,6 +31,7 @@ import java.util.Locale; /** * This class accesses a dictionary of corrections to frequent misspellings. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AutoText { // struct trie { // char c; diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java index dfa172df72ea..6d4103cee7a6 100644 --- a/core/java/android/text/BidiFormatter.java +++ b/core/java/android/text/BidiFormatter.java @@ -82,6 +82,7 @@ import java.util.Locale; * first-strong estimation algorithm. It can also be configured to use a custom directionality * estimation object. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class BidiFormatter { /** diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index 4fdcecc4f782..2b410e6284bf 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -45,6 +45,7 @@ import com.android.text.flags.Flags; * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) * Canvas.drawText()} directly.</p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback { /** diff --git a/core/java/android/text/CharSequenceCharacterIterator.java b/core/java/android/text/CharSequenceCharacterIterator.java index 9b07d29bd9dd..1599be8237d4 100644 --- a/core/java/android/text/CharSequenceCharacterIterator.java +++ b/core/java/android/text/CharSequenceCharacterIterator.java @@ -24,6 +24,7 @@ import java.text.CharacterIterator; * An implementation of {@link java.text.CharacterIterator} that iterates over a given CharSequence. * {@hide} */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class CharSequenceCharacterIterator implements CharacterIterator { private final int mBeginIndex, mEndIndex; private int mIndex; diff --git a/core/java/android/text/ClipboardManager.java b/core/java/android/text/ClipboardManager.java index d0309100b0f2..41990f0fc8dd 100644 --- a/core/java/android/text/ClipboardManager.java +++ b/core/java/android/text/ClipboardManager.java @@ -21,6 +21,7 @@ package android.text; * {@link android.content.ClipboardManager} for the modern API. */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class ClipboardManager { /** * Returns the text on the clipboard. It will eventually be possible diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 6b1aef710e50..3b66ce0167c4 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -53,6 +53,7 @@ import java.lang.ref.WeakReference; * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) * Canvas.drawText()} directly.</p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DynamicLayout extends Layout { private static final int PRIORITY = 128; private static final int BLOCK_MINIMUM_CHARACTER_LENGTH = 400; diff --git a/core/java/android/text/Editable.java b/core/java/android/text/Editable.java index a942f6ce2879..53d819f52a5c 100644 --- a/core/java/android/text/Editable.java +++ b/core/java/android/text/Editable.java @@ -22,6 +22,7 @@ package android.text; * to immutable text like Strings). If you make a {@link DynamicLayout} * of an Editable, the layout will be reflowed as the text is changed. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface Editable extends CharSequence, GetChars, Spannable, Appendable { diff --git a/core/java/android/text/Emoji.java b/core/java/android/text/Emoji.java index cf0e3c26daac..28c37c00d66e 100644 --- a/core/java/android/text/Emoji.java +++ b/core/java/android/text/Emoji.java @@ -23,6 +23,7 @@ import android.icu.lang.UProperty; * An utility class for Emoji. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Emoji { public static int COMBINING_ENCLOSING_KEYCAP = 0x20E3; diff --git a/core/java/android/text/EmojiConsistency.java b/core/java/android/text/EmojiConsistency.java index dfaa217c0cca..9823305ec72a 100644 --- a/core/java/android/text/EmojiConsistency.java +++ b/core/java/android/text/EmojiConsistency.java @@ -48,6 +48,7 @@ import java.util.Set; * </ol> * </p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class EmojiConsistency { /* Cannot construct */ private EmojiConsistency() { } diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 783f3b7aa64b..5a4d3a867032 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -55,6 +55,7 @@ import java.util.Objects; */ @SystemApi @TestApi +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class FontConfig implements Parcelable { private final @NonNull List<FontFamily> mFamilies; private final @NonNull List<Alias> mAliases; diff --git a/core/java/android/text/GetChars.java b/core/java/android/text/GetChars.java index 348a911a442f..229f5437e76b 100644 --- a/core/java/android/text/GetChars.java +++ b/core/java/android/text/GetChars.java @@ -21,6 +21,7 @@ package android.text; * getChars() method like the one in String that is faster than * calling charAt() multiple times. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface GetChars extends CharSequence { diff --git a/core/java/android/text/GraphemeClusterSegmentFinder.java b/core/java/android/text/GraphemeClusterSegmentFinder.java index 0f6fdaf23c65..996223dd1a84 100644 --- a/core/java/android/text/GraphemeClusterSegmentFinder.java +++ b/core/java/android/text/GraphemeClusterSegmentFinder.java @@ -31,6 +31,7 @@ import android.graphics.text.GraphemeBreak; * @see <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">Unicode Text * Segmentation - Grapheme Cluster Boundaries</a> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class GraphemeClusterSegmentFinder extends SegmentFinder { private static AutoGrowArray.FloatArray sTempAdvances = null; private final boolean[] mIsGraphemeBreak; diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java index 6c1544644eab..f7fe805e53a4 100644 --- a/core/java/android/text/GraphicsOperations.java +++ b/core/java/android/text/GraphicsOperations.java @@ -26,6 +26,7 @@ import android.graphics.Paint; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface GraphicsOperations extends CharSequence { /** * Just like {@link Canvas#drawText}. diff --git a/core/java/android/text/Highlights.java b/core/java/android/text/Highlights.java index 693dbcf84e84..217a38b6edd5 100644 --- a/core/java/android/text/Highlights.java +++ b/core/java/android/text/Highlights.java @@ -30,6 +30,7 @@ import java.util.Objects; /** * A class that represents of the highlight of the text. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Highlights { private final List<Pair<Paint, int[]>> mHighlights; diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index a42eece57eec..d412071bf3b9 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -17,13 +17,13 @@ package android.text; import android.app.ActivityThread; -import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; +import android.ravenwood.annotation.RavenwoodReplace; import android.text.style.AbsoluteSizeSpan; import android.text.style.AlignmentSpan; import android.text.style.BackgroundColorSpan; @@ -65,6 +65,7 @@ import java.util.regex.Pattern; * This class processes HTML strings into displayable styled text. * Not all HTML tags are supported. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Html { /** * Retrieves images for HTML <img> tags. @@ -506,6 +507,15 @@ public class Html { out.append("</p>\n"); } + @RavenwoodReplace(blockedBy = ActivityThread.class) + private static float getDisplayMetricsDensity() { + return ActivityThread.currentApplication().getResources().getDisplayMetrics().density; + } + + private static float getDisplayMetricsDensity$ravenwood() { + return Resources.getSystem().getDisplayMetrics().density; + } + private static void withinParagraph(StringBuilder out, Spanned text, int start, int end) { int next; for (int i = start; i < end; i = next) { @@ -559,8 +569,7 @@ public class Html { AbsoluteSizeSpan s = ((AbsoluteSizeSpan) style[j]); float sizeDip = s.getSize(); if (!s.getDip()) { - Application application = ActivityThread.currentApplication(); - sizeDip /= application.getResources().getDisplayMetrics().density; + sizeDip /= getDisplayMetricsDensity(); } // px in CSS is the equivalance of dip in Android @@ -669,6 +678,7 @@ public class Html { } } +@android.ravenwood.annotation.RavenwoodKeepWholeClass class HtmlToSpannedConverter implements ContentHandler { private static final float[] HEADING_SIZES = { @@ -843,6 +853,16 @@ class HtmlToSpannedConverter implements ContentHandler { } } + @RavenwoodReplace(blockedBy = ActivityThread.class) + private static int getFontWeightAdjustment() { + return ActivityThread.currentApplication().getResources() + .getConfiguration().fontWeightAdjustment; + } + + private static int getFontWeightAdjustment$ravenwood() { + return Resources.getSystem().getConfiguration().fontWeightAdjustment; + } + private void handleEndTag(String tag) { if (tag.equalsIgnoreCase("br")) { handleBr(mSpannableStringBuilder); @@ -858,17 +878,11 @@ class HtmlToSpannedConverter implements ContentHandler { } else if (tag.equalsIgnoreCase("span")) { endCssStyle(mSpannableStringBuilder); } else if (tag.equalsIgnoreCase("strong")) { - Application application = ActivityThread.currentApplication(); - int fontWeightAdjustment = - application.getResources().getConfiguration().fontWeightAdjustment; end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD, - fontWeightAdjustment)); + getFontWeightAdjustment())); } else if (tag.equalsIgnoreCase("b")) { - Application application = ActivityThread.currentApplication(); - int fontWeightAdjustment = - application.getResources().getConfiguration().fontWeightAdjustment; end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD, - fontWeightAdjustment)); + getFontWeightAdjustment())); } else if (tag.equalsIgnoreCase("em")) { end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); } else if (tag.equalsIgnoreCase("cite")) { @@ -1036,11 +1050,8 @@ class HtmlToSpannedConverter implements ContentHandler { // Their ranges should not include the newlines at the end Heading h = getLast(text, Heading.class); if (h != null) { - Application application = ActivityThread.currentApplication(); - int fontWeightAdjustment = - application.getResources().getConfiguration().fontWeightAdjustment; setSpanFromMark(text, h, new RelativeSizeSpan(HEADING_SIZES[h.mLevel]), - new StyleSpan(Typeface.BOLD, fontWeightAdjustment)); + new StyleSpan(Typeface.BOLD, getFontWeightAdjustment())); } endBlockElement(text); diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java index 6f0628ad38e6..7f9a8a1c0806 100644 --- a/core/java/android/text/Hyphenator.java +++ b/core/java/android/text/Hyphenator.java @@ -21,6 +21,7 @@ package android.text; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Hyphenator { private Hyphenator() {} diff --git a/core/java/android/text/InputFilter.java b/core/java/android/text/InputFilter.java index 96e7bd0fef4c..ed5de03e5528 100644 --- a/core/java/android/text/InputFilter.java +++ b/core/java/android/text/InputFilter.java @@ -27,6 +27,7 @@ import java.util.Locale; * InputFilters can be attached to {@link Editable}s to constrain the * changes that can be made to them. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface InputFilter { /** diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java index 4ebecb7494fa..03c9c023d9ce 100644 --- a/core/java/android/text/InputType.java +++ b/core/java/android/text/InputType.java @@ -44,6 +44,7 @@ import java.util.List; * TYPE_DATETIME_VARIATION_TIME * </dl> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface InputType { /** * Mask of bits that determine the overall class diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index b273a7f7c271..44c3f9a8244e 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -69,6 +69,7 @@ import java.util.Locale; * which will be updated as the text changes. * For text that will not change, use a {@link StaticLayout}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class Layout { // These should match the constants in framework/base/libs/hwui/hwui/DrawTextFunctor.h diff --git a/core/java/android/text/LoginFilter.java b/core/java/android/text/LoginFilter.java index 0e4eec4488ee..94f196f7ef6b 100644 --- a/core/java/android/text/LoginFilter.java +++ b/core/java/android/text/LoginFilter.java @@ -23,6 +23,7 @@ package android.text; * handle non-BMP characters. */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class LoginFilter implements InputFilter { private boolean mAppendInvalid; // whether to append or ignore invalid characters /** diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index 31a226341907..b2e44598a548 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -68,6 +68,7 @@ import java.util.Arrays; * @hide */ @TestApi +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class MeasuredParagraph { private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; diff --git a/core/java/android/text/NoCopySpan.java b/core/java/android/text/NoCopySpan.java index e754d765e14c..4cd3d04dc4e6 100644 --- a/core/java/android/text/NoCopySpan.java +++ b/core/java/android/text/NoCopySpan.java @@ -21,6 +21,7 @@ package android.text; * into a new Spanned when performing a slice or copy operation on the original * Spanned it was placed in. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface NoCopySpan { /** * Convenience equivalent for when you would just want a new Object() for diff --git a/core/java/android/text/PackedIntVector.java b/core/java/android/text/PackedIntVector.java index 3e5bf5677853..11dd0c38182b 100644 --- a/core/java/android/text/PackedIntVector.java +++ b/core/java/android/text/PackedIntVector.java @@ -29,6 +29,7 @@ import com.android.internal.util.GrowingArrayUtils; * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PackedIntVector { private final int mColumns; private int mRows; diff --git a/core/java/android/text/PackedObjectVector.java b/core/java/android/text/PackedObjectVector.java index b777e16a153d..beb5ea4ee28c 100644 --- a/core/java/android/text/PackedObjectVector.java +++ b/core/java/android/text/PackedObjectVector.java @@ -21,6 +21,7 @@ import com.android.internal.util.GrowingArrayUtils; import libcore.util.EmptyArray; +@android.ravenwood.annotation.RavenwoodKeepWholeClass class PackedObjectVector<E> { private int mColumns; diff --git a/core/java/android/text/ParcelableSpan.java b/core/java/android/text/ParcelableSpan.java index d7c1a4bc26e8..a9a4893d3692 100644 --- a/core/java/android/text/ParcelableSpan.java +++ b/core/java/android/text/ParcelableSpan.java @@ -24,6 +24,7 @@ import android.os.Parcelable; * This can only be used by code in the framework; it is not intended for * applications to implement their own Parcelable spans. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface ParcelableSpan extends Parcelable { /** * Return a special type identifier for this span class. diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index 5f6a9bd068c9..71cacd9b199a 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -75,6 +75,7 @@ import java.util.Objects; * Note that any {@link android.text.NoCopySpan} attached to the original text won't be passed to * PrecomputedText. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PrecomputedText implements Spannable { private static final char LINE_FEED = '\n'; diff --git a/core/java/android/text/SegmentFinder.java b/core/java/android/text/SegmentFinder.java index 047d07a2e3e0..b7ab0e62753a 100644 --- a/core/java/android/text/SegmentFinder.java +++ b/core/java/android/text/SegmentFinder.java @@ -39,6 +39,7 @@ import java.util.Objects; * * @see Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy) */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class SegmentFinder { /** * Return value of previousStartBoundary(int), previousEndBoundary(int), nextStartBoundary(int), diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java index 711578c1482f..674b47372482 100644 --- a/core/java/android/text/Selection.java +++ b/core/java/android/text/Selection.java @@ -27,6 +27,7 @@ import java.text.BreakIterator; * Utility class for manipulating cursors and selections in CharSequences. * A cursor is a selection where the start and end are at the same offset. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Selection { private Selection() { /* cannot be instantiated */ } diff --git a/core/java/android/text/SpanColors.java b/core/java/android/text/SpanColors.java index fcd242b62700..3b6a0418dcb0 100644 --- a/core/java/android/text/SpanColors.java +++ b/core/java/android/text/SpanColors.java @@ -27,6 +27,7 @@ import android.text.style.CharacterStyle; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SpanColors { public static final @ColorInt int NO_COLOR_FOUND = Color.TRANSPARENT; diff --git a/core/java/android/text/SpanSet.java b/core/java/android/text/SpanSet.java index d464278c714c..4ad8106eb459 100644 --- a/core/java/android/text/SpanSet.java +++ b/core/java/android/text/SpanSet.java @@ -31,6 +31,7 @@ import java.util.Arrays; * Note that empty spans are ignored by this class. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SpanSet<E> { private final Class<? extends E> classType; diff --git a/core/java/android/text/SpanWatcher.java b/core/java/android/text/SpanWatcher.java index 01e82c815ac8..31d63206a144 100644 --- a/core/java/android/text/SpanWatcher.java +++ b/core/java/android/text/SpanWatcher.java @@ -21,6 +21,7 @@ package android.text; * will be called to notify it that other markup objects have been * added, changed, or removed. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface SpanWatcher extends NoCopySpan { /** * This method is called to notify you that the specified object diff --git a/core/java/android/text/Spannable.java b/core/java/android/text/Spannable.java index 8315b2aa52c6..fac5131c035a 100644 --- a/core/java/android/text/Spannable.java +++ b/core/java/android/text/Spannable.java @@ -21,6 +21,7 @@ package android.text; * attached and detached. Not all Spannable classes have mutable text; * see {@link Editable} for that. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface Spannable extends Spanned { diff --git a/core/java/android/text/SpannableString.java b/core/java/android/text/SpannableString.java index afb5df809bc0..ee04a86a808f 100644 --- a/core/java/android/text/SpannableString.java +++ b/core/java/android/text/SpannableString.java @@ -21,6 +21,7 @@ package android.text; * markup objects can be attached and detached. * For mutable text, see {@link SpannableStringBuilder}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SpannableString extends SpannableStringInternal implements CharSequence, GetChars, Spannable diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index 0e61eff86c2b..f8d7283a94b3 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -35,6 +35,7 @@ import java.util.IdentityHashMap; /** * This is the class for text whose content and markup can both be changed. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable, Appendable, GraphicsOperations { private final static String TAG = "SpannableStringBuilder"; diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java index f2ab1bb31fbc..90d83d59eb65 100644 --- a/core/java/android/text/SpannableStringInternal.java +++ b/core/java/android/text/SpannableStringInternal.java @@ -27,6 +27,7 @@ import libcore.util.EmptyArray; import java.lang.reflect.Array; +@android.ravenwood.annotation.RavenwoodKeepWholeClass /* package */ abstract class SpannableStringInternal { /* package */ SpannableStringInternal(CharSequence source, diff --git a/core/java/android/text/Spanned.java b/core/java/android/text/Spanned.java index a0d54c26c6d9..6706ffd245c1 100644 --- a/core/java/android/text/Spanned.java +++ b/core/java/android/text/Spanned.java @@ -22,6 +22,7 @@ package android.text; * see {@link Spannable} for mutable markup and {@link Editable} for * mutable text. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface Spanned extends CharSequence { diff --git a/core/java/android/text/SpannedString.java b/core/java/android/text/SpannedString.java index acee3c5f1a41..a3f1ee2e3f20 100644 --- a/core/java/android/text/SpannedString.java +++ b/core/java/android/text/SpannedString.java @@ -22,6 +22,7 @@ package android.text; * For mutable markup, see {@link SpannableString}; for mutable text, * see {@link SpannableStringBuilder}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class SpannedString extends SpannableStringInternal implements CharSequence, GetChars, Spanned diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index a5d52957c40e..8193cd2beb80 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -55,6 +55,7 @@ import java.util.Arrays; * float, float, android.graphics.Paint) * Canvas.drawText()} directly.</p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class StaticLayout extends Layout { /* * The break iteration is done in native code. The protocol for using the native code is as diff --git a/core/java/android/text/TextDirectionHeuristic.java b/core/java/android/text/TextDirectionHeuristic.java index 8a4ba42bcc91..66cea853cda9 100644 --- a/core/java/android/text/TextDirectionHeuristic.java +++ b/core/java/android/text/TextDirectionHeuristic.java @@ -19,6 +19,7 @@ package android.text; /** * Interface for objects that use a heuristic for guessing at the paragraph direction by examining text. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface TextDirectionHeuristic { /** * Guess if a chars array is in the RTL direction or not. diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java index 85260f4af2c8..3af8fb7f489d 100644 --- a/core/java/android/text/TextDirectionHeuristics.java +++ b/core/java/android/text/TextDirectionHeuristics.java @@ -32,6 +32,7 @@ import java.nio.CharBuffer; * class. * */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TextDirectionHeuristics { /** diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 3015791ee0a9..091eb6027002 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -53,6 +53,7 @@ import java.util.ArrayList; * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TextLine { private static final boolean DEBUG = false; diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java index 73825b13cb6b..ff063f2ac2f4 100644 --- a/core/java/android/text/TextPaint.java +++ b/core/java/android/text/TextPaint.java @@ -25,6 +25,7 @@ import android.graphics.Paint; * TextPaint is an extension of Paint that leaves room for some extra * data used during text measuring and drawing. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TextPaint extends Paint { // Special value 0 means no background paint diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java index 6da0b63dbc1f..6d1740184763 100644 --- a/core/java/android/text/TextShaper.java +++ b/core/java/android/text/TextShaper.java @@ -169,6 +169,7 @@ import android.graphics.text.TextRunShaper; * @see TextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint, * GlyphsConsumer) */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TextShaper { private TextShaper() {} diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 6dc82c40ddc5..042966b81b9a 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -34,6 +34,7 @@ import android.icu.text.Edits; import android.icu.util.ULocale; import android.os.Parcel; import android.os.Parcelable; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.sysprop.DisplayProperties; import android.text.style.AbsoluteSizeSpan; import android.text.style.AccessibilityClickableSpan; @@ -85,8 +86,7 @@ import java.util.List; import java.util.Locale; import java.util.regex.Pattern; -@android.ravenwood.annotation.RavenwoodKeepStaticInitializer -@android.ravenwood.annotation.RavenwoodKeepPartialClass +@RavenwoodKeepWholeClass public class TextUtils { private static final String TAG = "TextUtils"; @@ -147,7 +147,6 @@ public class TextUtils { private TextUtils() { /* cannot be instantiated */ } - @android.ravenwood.annotation.RavenwoodKeep public static void getChars(CharSequence s, int start, int end, char[] dest, int destoff) { Class<? extends CharSequence> c = s.getClass(); @@ -166,12 +165,10 @@ public class TextUtils { } } - @android.ravenwood.annotation.RavenwoodKeep public static int indexOf(CharSequence s, char ch) { return indexOf(s, ch, 0); } - @android.ravenwood.annotation.RavenwoodKeep public static int indexOf(CharSequence s, char ch, int start) { Class<? extends CharSequence> c = s.getClass(); @@ -181,7 +178,6 @@ public class TextUtils { return indexOf(s, ch, start, s.length()); } - @android.ravenwood.annotation.RavenwoodKeep public static int indexOf(CharSequence s, char ch, int start, int end) { Class<? extends CharSequence> c = s.getClass(); @@ -219,12 +215,10 @@ public class TextUtils { return -1; } - @android.ravenwood.annotation.RavenwoodKeep public static int lastIndexOf(CharSequence s, char ch) { return lastIndexOf(s, ch, s.length() - 1); } - @android.ravenwood.annotation.RavenwoodKeep public static int lastIndexOf(CharSequence s, char ch, int last) { Class<? extends CharSequence> c = s.getClass(); @@ -234,7 +228,6 @@ public class TextUtils { return lastIndexOf(s, ch, 0, last); } - @android.ravenwood.annotation.RavenwoodKeep public static int lastIndexOf(CharSequence s, char ch, int start, int last) { if (last < 0) @@ -280,17 +273,14 @@ public class TextUtils { return -1; } - @android.ravenwood.annotation.RavenwoodKeep public static int indexOf(CharSequence s, CharSequence needle) { return indexOf(s, needle, 0, s.length()); } - @android.ravenwood.annotation.RavenwoodKeep public static int indexOf(CharSequence s, CharSequence needle, int start) { return indexOf(s, needle, start, s.length()); } - @android.ravenwood.annotation.RavenwoodKeep public static int indexOf(CharSequence s, CharSequence needle, int start, int end) { int nlen = needle.length(); @@ -318,7 +308,6 @@ public class TextUtils { return -1; } - @android.ravenwood.annotation.RavenwoodKeep public static boolean regionMatches(CharSequence one, int toffset, CharSequence two, int ooffset, int len) { @@ -351,7 +340,6 @@ public class TextUtils { * in that it does not preserve any style runs in the source sequence, * allowing a more efficient implementation. */ - @android.ravenwood.annotation.RavenwoodKeep public static String substring(CharSequence source, int start, int end) { if (source instanceof String) return ((String) source).substring(start, end); @@ -424,7 +412,6 @@ public class TextUtils { * calling object.toString(). If tokens is null, a NullPointerException will be thrown. If * tokens is an empty array, an empty string will be returned. */ - @android.ravenwood.annotation.RavenwoodKeep public static String join(@NonNull CharSequence delimiter, @NonNull Object[] tokens) { final int length = tokens.length; if (length == 0) { @@ -448,7 +435,6 @@ public class TextUtils { * calling object.toString(). If tokens is null, a NullPointerException will be thrown. If * tokens is empty, an empty string will be returned. */ - @android.ravenwood.annotation.RavenwoodKeep public static String join(@NonNull CharSequence delimiter, @NonNull Iterable tokens) { final Iterator<?> it = tokens.iterator(); if (!it.hasNext()) { @@ -481,7 +467,6 @@ public class TextUtils { * * @throws NullPointerException if expression or text is null */ - @android.ravenwood.annotation.RavenwoodKeep public static String[] split(String text, String expression) { if (text.length() == 0) { return EmptyArray.STRING; @@ -507,7 +492,6 @@ public class TextUtils { * * @throws NullPointerException if expression or text is null */ - @android.ravenwood.annotation.RavenwoodKeep public static String[] split(String text, Pattern pattern) { if (text.length() == 0) { return EmptyArray.STRING; @@ -545,7 +529,6 @@ public class TextUtils { * be returned for the empty string after that delimeter. That is, splitting <tt>"a,b,"</tt> on * comma will return <tt>"a", "b"</tt>, not <tt>"a", "b", ""</tt>. */ - @android.ravenwood.annotation.RavenwoodKeepWholeClass public static class SimpleStringSplitter implements StringSplitter, Iterator<String> { private String mString; private char mDelimiter; @@ -609,31 +592,26 @@ public class TextUtils { * @param str the string to be examined * @return true if str is null or zero length */ - @android.ravenwood.annotation.RavenwoodKeep public static boolean isEmpty(@Nullable CharSequence str) { return str == null || str.length() == 0; } /** {@hide} */ - @android.ravenwood.annotation.RavenwoodKeep public static String nullIfEmpty(@Nullable String str) { return isEmpty(str) ? null : str; } /** {@hide} */ - @android.ravenwood.annotation.RavenwoodKeep public static String emptyIfNull(@Nullable String str) { return str == null ? "" : str; } /** {@hide} */ - @android.ravenwood.annotation.RavenwoodKeep public static String firstNotEmpty(@Nullable String a, @NonNull String b) { return !isEmpty(a) ? a : Preconditions.checkStringNotEmpty(b); } /** {@hide} */ - @android.ravenwood.annotation.RavenwoodKeep public static int length(@Nullable String s) { return s != null ? s.length() : 0; } @@ -642,7 +620,6 @@ public class TextUtils { * @return interned string if it's null. * @hide */ - @android.ravenwood.annotation.RavenwoodKeep public static String safeIntern(String s) { return (s != null) ? s.intern() : null; } @@ -652,7 +629,6 @@ public class TextUtils { * spaces and ASCII control characters were trimmed from the start and end, * as by {@link String#trim}. */ - @android.ravenwood.annotation.RavenwoodKeep public static int getTrimmedLength(CharSequence s) { int len = s.length(); @@ -677,7 +653,6 @@ public class TextUtils { * @param b second CharSequence to check * @return true if a and b are equal */ - @android.ravenwood.annotation.RavenwoodKeep public static boolean equals(@Nullable CharSequence a, @Nullable CharSequence b) { if (a == b) return true; int length; @@ -1713,7 +1688,6 @@ public class TextUtils { return true; } - @android.ravenwood.annotation.RavenwoodKeep /* package */ static char[] obtain(int len) { char[] buf; @@ -1728,7 +1702,6 @@ public class TextUtils { return buf; } - @android.ravenwood.annotation.RavenwoodKeep /* package */ static void recycle(char[] temp) { if (temp.length > 1000) return; @@ -1743,7 +1716,6 @@ public class TextUtils { * @param s the string to be encoded * @return the encoded string */ - @android.ravenwood.annotation.RavenwoodKeep public static String htmlEncode(String s) { StringBuilder sb = new StringBuilder(); char c; @@ -1830,7 +1802,6 @@ public class TextUtils { /** * Returns whether the given CharSequence contains any printable characters. */ - @android.ravenwood.annotation.RavenwoodKeep public static boolean isGraphic(CharSequence str) { final int len = str.length(); for (int cp, i=0; i<len; i+=Character.charCount(cp)) { @@ -1857,7 +1828,6 @@ public class TextUtils { * @deprecated Use {@link #isGraphic(CharSequence)} instead. */ @Deprecated - @android.ravenwood.annotation.RavenwoodKeep public static boolean isGraphic(char c) { int gc = Character.getType(c); return gc != Character.CONTROL @@ -1872,7 +1842,6 @@ public class TextUtils { /** * Returns whether the given CharSequence contains only digits. */ - @android.ravenwood.annotation.RavenwoodKeep public static boolean isDigitsOnly(CharSequence str) { final int len = str.length(); for (int cp, i = 0; i < len; i += Character.charCount(cp)) { @@ -1887,7 +1856,6 @@ public class TextUtils { /** * @hide */ - @android.ravenwood.annotation.RavenwoodKeep public static boolean isPrintableAscii(final char c) { final int asciiFirst = 0x20; final int asciiLast = 0x7E; // included @@ -1898,7 +1866,6 @@ public class TextUtils { * @hide */ @UnsupportedAppUsage - @android.ravenwood.annotation.RavenwoodKeep public static boolean isPrintableAsciiOnly(final CharSequence str) { final int len = str.length(); for (int i = 0; i < len; i++) { @@ -1950,7 +1917,6 @@ public class TextUtils { * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and * {@link #CAP_MODE_SENTENCES}. */ - @android.ravenwood.annotation.RavenwoodKeep public static int getCapsMode(CharSequence cs, int off, int reqModes) { if (off < 0) { return 0; @@ -2162,7 +2128,6 @@ public class TextUtils { * * Be careful: this code will need to be updated when vertical scripts will be supported */ - @android.ravenwood.annotation.RavenwoodKeep public static int getLayoutDirectionFromLocale(Locale locale) { return ((locale != null && !locale.equals(Locale.ROOT) && ULocale.forLocale(locale).isRightToLeft()) @@ -2197,7 +2162,6 @@ public class TextUtils { * match the supported grammar described above. * @hide */ - @android.ravenwood.annotation.RavenwoodKeep public static @NonNull String formatSimple(@NonNull String format, Object... args) { final StringBuilder sb = new StringBuilder(format); int j = 0; @@ -2387,7 +2351,6 @@ public class TextUtils { } /** @hide */ - @android.ravenwood.annotation.RavenwoodKeep public static boolean isNewline(int codePoint) { int type = Character.getType(codePoint); return type == Character.PARAGRAPH_SEPARATOR || type == Character.LINE_SEPARATOR @@ -2395,19 +2358,16 @@ public class TextUtils { } /** @hide */ - @android.ravenwood.annotation.RavenwoodKeep public static boolean isWhitespace(int codePoint) { return Character.isWhitespace(codePoint) || codePoint == NBSP_CODE_POINT; } /** @hide */ - @android.ravenwood.annotation.RavenwoodKeep public static boolean isWhitespaceExceptNewline(int codePoint) { return isWhitespace(codePoint) && !isNewline(codePoint); } /** @hide */ - @android.ravenwood.annotation.RavenwoodKeep public static boolean isPunctuation(int codePoint) { int type = Character.getType(codePoint); return type == Character.CONNECTOR_PUNCTUATION diff --git a/core/java/android/text/TextWatcher.java b/core/java/android/text/TextWatcher.java index a0aef690a1b8..5963ca7d5083 100644 --- a/core/java/android/text/TextWatcher.java +++ b/core/java/android/text/TextWatcher.java @@ -20,6 +20,7 @@ package android.text; * When an object of this type is attached to an Editable, its methods will * be called when the text is changed. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface TextWatcher extends NoCopySpan { /** * This method is called to notify you that, within <code>s</code>, diff --git a/core/java/android/text/WordSegmentFinder.java b/core/java/android/text/WordSegmentFinder.java index b0a70eae902a..b8702d72f29c 100644 --- a/core/java/android/text/WordSegmentFinder.java +++ b/core/java/android/text/WordSegmentFinder.java @@ -33,6 +33,7 @@ import android.text.method.WordIterator; * @see <a href="https://unicode.org/reports/tr29/#Word_Boundaries">Unicode Text Segmentation - Word * Boundaries</a> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class WordSegmentFinder extends SegmentFinder { private final CharSequence mText; private final WordIterator mWordIterator; diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index e6dad27d595b..ef60d3058c5c 100644 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -63,6 +63,7 @@ import java.util.TimeZone; * Note that the non-{@code format} methods in this class are implemented by * {@code SimpleDateFormat}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DateFormat { /** * @deprecated Use a literal {@code '} instead. diff --git a/core/java/android/text/format/DateIntervalFormat.java b/core/java/android/text/format/DateIntervalFormat.java index 8dea3228eb0c..5ec9561b2315 100644 --- a/core/java/android/text/format/DateIntervalFormat.java +++ b/core/java/android/text/format/DateIntervalFormat.java @@ -37,6 +37,7 @@ import java.util.TimeZone; * @hide */ @VisibleForTesting(visibility = PACKAGE) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class DateIntervalFormat { private static final LruCache<String, android.icu.text.DateIntervalFormat> CACHED_FORMATTERS = diff --git a/core/java/android/text/format/DateTimeFormat.java b/core/java/android/text/format/DateTimeFormat.java index 064d7172c44f..c8dd61d0d75c 100644 --- a/core/java/android/text/format/DateTimeFormat.java +++ b/core/java/android/text/format/DateTimeFormat.java @@ -29,6 +29,7 @@ import android.util.LruCache; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass class DateTimeFormat { private static final FormatterCache CACHED_FORMATTERS = new FormatterCache(); diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index 518a5498d6ed..12ad76454870 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -44,6 +44,7 @@ import java.util.TimeZone; * This class contains various date-related utilities for creating text for things like * elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DateUtils { private static final Object sLock = new Object(); diff --git a/core/java/android/text/format/DateUtilsBridge.java b/core/java/android/text/format/DateUtilsBridge.java index 92ec9cf6d736..752a8c0ef40a 100644 --- a/core/java/android/text/format/DateUtilsBridge.java +++ b/core/java/android/text/format/DateUtilsBridge.java @@ -46,6 +46,7 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ @VisibleForTesting(visibility = PACKAGE) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class DateUtilsBridge { /** diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java index 7653bdb7b2d8..e7783dcb2630 100644 --- a/core/java/android/text/format/Formatter.java +++ b/core/java/android/text/format/Formatter.java @@ -41,6 +41,7 @@ import java.util.Locale; * Utility class to aid in formatting common values that are not covered * by the {@link java.util.Formatter} class in {@link java.util} */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Formatter { /** {@hide} */ diff --git a/core/java/android/text/format/RelativeDateTimeFormatter.java b/core/java/android/text/format/RelativeDateTimeFormatter.java index 9096469699c1..6b940f8cb380 100644 --- a/core/java/android/text/format/RelativeDateTimeFormatter.java +++ b/core/java/android/text/format/RelativeDateTimeFormatter.java @@ -42,6 +42,7 @@ import java.util.Locale; * @hide */ @VisibleForTesting(visibility = PACKAGE) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class RelativeDateTimeFormatter { public static final long SECOND_IN_MILLIS = 1000; diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index bac7c6cf87d3..1beb57389ef5 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -53,6 +53,7 @@ import java.util.TimeZone; * @deprecated Use {@link java.util.GregorianCalendar} instead. */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Time { private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000"; private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z"; diff --git a/core/java/android/text/format/TimeFormatter.java b/core/java/android/text/format/TimeFormatter.java index e42ad6334649..dd703d85624f 100644 --- a/core/java/android/text/format/TimeFormatter.java +++ b/core/java/android/text/format/TimeFormatter.java @@ -40,6 +40,7 @@ import java.util.TimeZone; * * <p>This class is not thread safe. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass class TimeFormatter { // An arbitrary value outside the range representable by a char. private static final int FORCE_LOWER_CASE = -1; diff --git a/core/java/android/text/format/TimeMigrationUtils.java b/core/java/android/text/format/TimeMigrationUtils.java index 17bac8d67b26..b2f5024c73b7 100644 --- a/core/java/android/text/format/TimeMigrationUtils.java +++ b/core/java/android/text/format/TimeMigrationUtils.java @@ -22,6 +22,7 @@ package android.text.format; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TimeMigrationUtils { private TimeMigrationUtils() {} diff --git a/core/java/android/text/method/AllCapsTransformationMethod.java b/core/java/android/text/method/AllCapsTransformationMethod.java index 305b056aee72..70dcc52691cd 100644 --- a/core/java/android/text/method/AllCapsTransformationMethod.java +++ b/core/java/android/text/method/AllCapsTransformationMethod.java @@ -33,6 +33,7 @@ import java.util.Locale; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AllCapsTransformationMethod implements TransformationMethod2 { private static final String TAG = "AllCapsTransformationMethod"; diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 37474e5645b0..609922073f53 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -30,6 +30,7 @@ import android.widget.TextView; * A movement method that provides cursor movement and selection. * Supports displaying the context menu on DPad Center. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ArrowKeyMovementMethod extends BaseMovementMethod implements MovementMethod { private static boolean isSelecting(Spannable buffer) { return ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SHIFT_ON) == 1) || diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java index e427908541e5..5ebfd99c6f6e 100644 --- a/core/java/android/text/method/BaseKeyListener.java +++ b/core/java/android/text/method/BaseKeyListener.java @@ -47,6 +47,7 @@ import java.text.BreakIterator; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class BaseKeyListener extends MetaKeyKeyListener implements KeyListener { /* package */ static final Object OLD_SEL_START = new NoCopySpan.Concrete(); diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java index 7a4b3a095ae9..0c2e52e04c1f 100644 --- a/core/java/android/text/method/BaseMovementMethod.java +++ b/core/java/android/text/method/BaseMovementMethod.java @@ -27,6 +27,7 @@ import android.widget.TextView; /** * Base classes for movement methods. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BaseMovementMethod implements MovementMethod { @Override public boolean canSelectArbitrarily() { diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java index 7d838e06cd68..f084d03cf6dd 100644 --- a/core/java/android/text/method/CharacterPickerDialog.java +++ b/core/java/android/text/method/CharacterPickerDialog.java @@ -38,6 +38,7 @@ import com.android.internal.R; /** * Dialog for choosing accented characters related to a base character. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class CharacterPickerDialog extends Dialog implements OnItemClickListener, OnClickListener { private View mView; diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java index 0accbf6c74e5..acf182263272 100644 --- a/core/java/android/text/method/DateKeyListener.java +++ b/core/java/android/text/method/DateKeyListener.java @@ -35,6 +35,7 @@ import java.util.Locale; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DateKeyListener extends NumberKeyListener { public int getInputType() { diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java index 1593db5de641..a46ae45433b9 100644 --- a/core/java/android/text/method/DateTimeKeyListener.java +++ b/core/java/android/text/method/DateTimeKeyListener.java @@ -35,6 +35,7 @@ import java.util.Locale; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DateTimeKeyListener extends NumberKeyListener { public int getInputType() { diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java index 17abed6c363a..9eea51a07593 100644 --- a/core/java/android/text/method/DialerKeyListener.java +++ b/core/java/android/text/method/DialerKeyListener.java @@ -28,6 +28,7 @@ import android.view.KeyEvent; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DialerKeyListener extends NumberKeyListener { @Override diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java index d9f2dcf2e896..c97d4afef4fa 100644 --- a/core/java/android/text/method/DigitsKeyListener.java +++ b/core/java/android/text/method/DigitsKeyListener.java @@ -40,6 +40,7 @@ import java.util.Locale; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DigitsKeyListener extends NumberKeyListener { private char[] mAccepted; diff --git a/core/java/android/text/method/HideReturnsTransformationMethod.java b/core/java/android/text/method/HideReturnsTransformationMethod.java index 40ce8714cf38..8b93b3558f86 100644 --- a/core/java/android/text/method/HideReturnsTransformationMethod.java +++ b/core/java/android/text/method/HideReturnsTransformationMethod.java @@ -24,6 +24,7 @@ import android.os.Build; * to be hidden by displaying them as zero-width non-breaking space * characters (\uFEFF). */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class HideReturnsTransformationMethod extends ReplacementTransformationMethod { private static char[] ORIGINAL = new char[] { '\r' }; diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java index 6c6576f8888e..ace2d256f5b0 100644 --- a/core/java/android/text/method/InsertModeTransformationMethod.java +++ b/core/java/android/text/method/InsertModeTransformationMethod.java @@ -58,6 +58,7 @@ import java.lang.reflect.Array; * the new transformed text: "hello abc\n\n world", and the highlight range will be [5, 11). * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class InsertModeTransformationMethod implements TransformationMethod, TextWatcher { /** The start offset of the highlight range in the original text, inclusive. */ private int mStart; diff --git a/core/java/android/text/method/KeyListener.java b/core/java/android/text/method/KeyListener.java index ce7054c40d01..447c4d9432ed 100644 --- a/core/java/android/text/method/KeyListener.java +++ b/core/java/android/text/method/KeyListener.java @@ -34,6 +34,7 @@ import android.view.View; * targetting Jelly Bean or later, and will only deliver it for some * key presses to applications targetting Ice Cream Sandwich or earlier. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface KeyListener { /** * Return the type of text that this key listener is manipulating, diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java index 9f4a0aea7207..484bc1ae1a85 100644 --- a/core/java/android/text/method/LinkMovementMethod.java +++ b/core/java/android/text/method/LinkMovementMethod.java @@ -33,6 +33,7 @@ import android.widget.TextView; * A movement method that traverses links in the text buffer and scrolls if necessary. * Supports clicking on links with DPad Center or Enter. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LinkMovementMethod extends ScrollingMovementMethod { private static final int CLICK = 1; private static final int UP = 2; diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java index d1d7c968411f..7c9c2f132793 100644 --- a/core/java/android/text/method/MetaKeyKeyListener.java +++ b/core/java/android/text/method/MetaKeyKeyListener.java @@ -71,6 +71,7 @@ import android.view.View; * } * </code> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class MetaKeyKeyListener { /** * Flag that indicates that the SHIFT key is on. diff --git a/core/java/android/text/method/MovementMethod.java b/core/java/android/text/method/MovementMethod.java index f6fe575a9265..5ea439d50561 100644 --- a/core/java/android/text/method/MovementMethod.java +++ b/core/java/android/text/method/MovementMethod.java @@ -32,6 +32,7 @@ import android.widget.TextView; * directly by applications. * </p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface MovementMethod { public void initialize(TextView widget, Spannable text); public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event); diff --git a/core/java/android/text/method/MultiTapKeyListener.java b/core/java/android/text/method/MultiTapKeyListener.java index 5770482b3feb..022853abf450 100644 --- a/core/java/android/text/method/MultiTapKeyListener.java +++ b/core/java/android/text/method/MultiTapKeyListener.java @@ -36,6 +36,7 @@ import android.view.View; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class MultiTapKeyListener extends BaseKeyListener implements SpanWatcher { private static MultiTapKeyListener[] sInstance = diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java index 2b038dd11348..e32ccd48c7e3 100644 --- a/core/java/android/text/method/NumberKeyListener.java +++ b/core/java/android/text/method/NumberKeyListener.java @@ -39,6 +39,7 @@ import java.util.Locale; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class NumberKeyListener extends BaseKeyListener implements InputFilter { diff --git a/core/java/android/text/method/OffsetMapping.java b/core/java/android/text/method/OffsetMapping.java index fcf3de6784fb..99613d3ae300 100644 --- a/core/java/android/text/method/OffsetMapping.java +++ b/core/java/android/text/method/OffsetMapping.java @@ -27,6 +27,7 @@ import java.lang.annotation.RetentionPolicy; * {@link TransformationMethod} that alters the text length. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface OffsetMapping { /** * The mapping strategy for a character offset. diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java index 53553be6e5a8..4a61d9aa1bd0 100644 --- a/core/java/android/text/method/PasswordTransformationMethod.java +++ b/core/java/android/text/method/PasswordTransformationMethod.java @@ -33,6 +33,7 @@ import android.view.View; import java.lang.ref.WeakReference; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PasswordTransformationMethod implements TransformationMethod, TextWatcher { diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java index c43864d0f215..27c58ea3a56e 100644 --- a/core/java/android/text/method/QwertyKeyListener.java +++ b/core/java/android/text/method/QwertyKeyListener.java @@ -37,6 +37,7 @@ import android.view.View; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class QwertyKeyListener extends BaseKeyListener { private static QwertyKeyListener[] sInstance = new QwertyKeyListener[Capitalize.values().length * 2]; diff --git a/core/java/android/text/method/ReplacementTransformationMethod.java b/core/java/android/text/method/ReplacementTransformationMethod.java index d6f879aa4353..05899d79b02e 100644 --- a/core/java/android/text/method/ReplacementTransformationMethod.java +++ b/core/java/android/text/method/ReplacementTransformationMethod.java @@ -30,6 +30,7 @@ import android.view.View; * array to be replaced by the corresponding characters in the * {@link #getReplacement} array. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class ReplacementTransformationMethod implements TransformationMethod { diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java index 4f422cbf51cb..2e0eda96b7f6 100644 --- a/core/java/android/text/method/ScrollingMovementMethod.java +++ b/core/java/android/text/method/ScrollingMovementMethod.java @@ -25,6 +25,7 @@ import android.widget.TextView; /** * A movement method that interprets movement keys by scrolling the text buffer. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ScrollingMovementMethod extends BaseMovementMethod implements MovementMethod { @Override protected boolean left(TextView widget, Spannable buffer) { diff --git a/core/java/android/text/method/SingleLineTransformationMethod.java b/core/java/android/text/method/SingleLineTransformationMethod.java index 818526a7d795..d6eff86920f8 100644 --- a/core/java/android/text/method/SingleLineTransformationMethod.java +++ b/core/java/android/text/method/SingleLineTransformationMethod.java @@ -21,6 +21,7 @@ package android.text.method; * displayed as spaces instead of causing line breaks, and causes * carriage return characters (\r) to have no appearance. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SingleLineTransformationMethod extends ReplacementTransformationMethod { private static char[] ORIGINAL = new char[] { '\n', '\r' }; diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java index 2eb917b6fd57..1b0ae61c62a4 100644 --- a/core/java/android/text/method/TextKeyListener.java +++ b/core/java/android/text/method/TextKeyListener.java @@ -43,6 +43,7 @@ import java.lang.ref.WeakReference; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TextKeyListener extends BaseKeyListener implements SpanWatcher { private static TextKeyListener[] sInstance = new TextKeyListener[Capitalize.values().length * 2]; diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java index f11f40099d9c..337611c79e3e 100644 --- a/core/java/android/text/method/TimeKeyListener.java +++ b/core/java/android/text/method/TimeKeyListener.java @@ -35,6 +35,7 @@ import java.util.Locale; * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TimeKeyListener extends NumberKeyListener { public int getInputType() { diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java index 44811cb3ef8b..85aadba5da2b 100644 --- a/core/java/android/text/method/Touch.java +++ b/core/java/android/text/method/Touch.java @@ -25,6 +25,7 @@ import android.view.MotionEvent; import android.view.ViewConfiguration; import android.widget.TextView; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Touch { private Touch() { } diff --git a/core/java/android/text/method/TransformationMethod.java b/core/java/android/text/method/TransformationMethod.java index 8f3b334abbbd..5246baa39d14 100644 --- a/core/java/android/text/method/TransformationMethod.java +++ b/core/java/android/text/method/TransformationMethod.java @@ -24,6 +24,7 @@ import android.view.View; * characters of passwords with dots, or keeping the newline characters * from causing line breaks in single-line text fields. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface TransformationMethod { /** diff --git a/core/java/android/text/method/TransformationMethod2.java b/core/java/android/text/method/TransformationMethod2.java index 8d5ec246640e..6e0feb419f00 100644 --- a/core/java/android/text/method/TransformationMethod2.java +++ b/core/java/android/text/method/TransformationMethod2.java @@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface TransformationMethod2 extends TransformationMethod { /** * Relax the contract of TransformationMethod to allow length changes, diff --git a/core/java/android/text/method/TranslationTransformationMethod.java b/core/java/android/text/method/TranslationTransformationMethod.java index 43d186ee9d21..8f43d3dd1f6f 100644 --- a/core/java/android/text/method/TranslationTransformationMethod.java +++ b/core/java/android/text/method/TranslationTransformationMethod.java @@ -33,6 +33,7 @@ import java.util.regex.Pattern; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TranslationTransformationMethod implements TransformationMethod2 { private static final String TAG = "TranslationTransformationMethod"; diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java index 2956f8461388..d57fa9b55312 100644 --- a/core/java/android/text/method/WordIterator.java +++ b/core/java/android/text/method/WordIterator.java @@ -37,6 +37,7 @@ import java.util.Locale; * Also provides methods to determine word boundaries. * {@hide} */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class WordIterator implements Selection.PositionIterator { // Size of the window for the word iterator, should be greater than the longest word's length private static final int WINDOW_WIDTH = 50; diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java index 6d4f05a84a03..1bc5d71fcd37 100644 --- a/core/java/android/text/style/AbsoluteSizeSpan.java +++ b/core/java/android/text/style/AbsoluteSizeSpan.java @@ -32,6 +32,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/absolutesizespan.png" /> * <figcaption>Text with text size updated.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableSpan { private final int mSize; diff --git a/core/java/android/text/style/AccessibilityClickableSpan.java b/core/java/android/text/style/AccessibilityClickableSpan.java index ee8d156f9aac..5741f2ae2864 100644 --- a/core/java/android/text/style/AccessibilityClickableSpan.java +++ b/core/java/android/text/style/AccessibilityClickableSpan.java @@ -43,6 +43,7 @@ import com.android.internal.R; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AccessibilityClickableSpan extends ClickableSpan implements ParcelableSpan { // The id of the span this one replaces diff --git a/core/java/android/text/style/AccessibilityReplacementSpan.java b/core/java/android/text/style/AccessibilityReplacementSpan.java index e4fc14790b53..af3a324668d0 100644 --- a/core/java/android/text/style/AccessibilityReplacementSpan.java +++ b/core/java/android/text/style/AccessibilityReplacementSpan.java @@ -31,6 +31,7 @@ import android.view.accessibility.AccessibilityNodeInfo; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AccessibilityReplacementSpan extends ReplacementSpan implements ParcelableSpan { diff --git a/core/java/android/text/style/AccessibilityURLSpan.java b/core/java/android/text/style/AccessibilityURLSpan.java index e280bdf8b339..1fb76e776b56 100644 --- a/core/java/android/text/style/AccessibilityURLSpan.java +++ b/core/java/android/text/style/AccessibilityURLSpan.java @@ -27,6 +27,7 @@ import android.view.accessibility.AccessibilityNodeInfo; * @hide */ @SuppressWarnings("ParcelableCreator") +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AccessibilityURLSpan extends URLSpan implements Parcelable { final AccessibilityClickableSpan mAccessibilityClickableSpan; diff --git a/core/java/android/text/style/AlignmentSpan.java b/core/java/android/text/style/AlignmentSpan.java index 31db78a51c75..53cbd63a32c7 100644 --- a/core/java/android/text/style/AlignmentSpan.java +++ b/core/java/android/text/style/AlignmentSpan.java @@ -25,6 +25,7 @@ import android.text.TextUtils; /** * Span that allows defining the alignment of text at the paragraph level. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface AlignmentSpan extends ParagraphStyle { /** diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java index 13647d92a897..bb04d0ff6178 100644 --- a/core/java/android/text/style/BackgroundColorSpan.java +++ b/core/java/android/text/style/BackgroundColorSpan.java @@ -34,6 +34,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/backgroundcolorspan.png" /> * <figcaption>Set a background color for the text.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BackgroundColorSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java index f70e6c56b5c9..24ae6e25fa59 100644 --- a/core/java/android/text/style/BulletSpan.java +++ b/core/java/android/text/style/BulletSpan.java @@ -63,6 +63,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/custombulletspan.png" /> * <figcaption>Customized BulletSpan.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BulletSpan implements LeadingMarginSpan, ParcelableSpan { // Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices. private static final int STANDARD_BULLET_RADIUS = 4; diff --git a/core/java/android/text/style/CharacterStyle.java b/core/java/android/text/style/CharacterStyle.java index 5b95f1a7816a..2ea05e6ecde0 100644 --- a/core/java/android/text/style/CharacterStyle.java +++ b/core/java/android/text/style/CharacterStyle.java @@ -23,6 +23,7 @@ import android.text.TextPaint; * class. Most extend its subclass {@link MetricAffectingSpan}, but simple * ones may just implement {@link UpdateAppearance}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class CharacterStyle { public abstract void updateDrawState(TextPaint tp); diff --git a/core/java/android/text/style/ClickableSpan.java b/core/java/android/text/style/ClickableSpan.java index 238da55526b0..9e35d75c8833 100644 --- a/core/java/android/text/style/ClickableSpan.java +++ b/core/java/android/text/style/ClickableSpan.java @@ -36,6 +36,7 @@ import android.view.View; * <img src="{@docRoot}reference/android/images/text/style/clickablespan.png" /> * <figcaption>Text with <code>ClickableSpan</code>.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance { private static int sIdCounter = 0; diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java index 5c9742622549..337c49fddf16 100644 --- a/core/java/android/text/style/ForegroundColorSpan.java +++ b/core/java/android/text/style/ForegroundColorSpan.java @@ -34,6 +34,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/foregroundcolorspan.png" /> * <figcaption>Set a text color.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ForegroundColorSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java index a6c513971ffb..cc946e98ece9 100644 --- a/core/java/android/text/style/IconMarginSpan.java +++ b/core/java/android/text/style/IconMarginSpan.java @@ -44,6 +44,7 @@ import android.text.Spanned; * @see DrawableMarginSpan for working with a {@link android.graphics.drawable.Drawable} instead of * a {@link Bitmap}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class IconMarginSpan implements LeadingMarginSpan, LineHeightSpan { @NonNull diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java index 5bd2d60bb34f..60c45784f680 100644 --- a/core/java/android/text/style/LeadingMarginSpan.java +++ b/core/java/android/text/style/LeadingMarginSpan.java @@ -32,6 +32,7 @@ import android.text.TextUtils; * LeadingMarginSpans should be attached from the first character to the last * character of a single paragraph. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface LeadingMarginSpan extends ParagraphStyle { diff --git a/core/java/android/text/style/LineBackgroundSpan.java b/core/java/android/text/style/LineBackgroundSpan.java index 7cb91477738e..c2d38ce92290 100644 --- a/core/java/android/text/style/LineBackgroundSpan.java +++ b/core/java/android/text/style/LineBackgroundSpan.java @@ -28,6 +28,7 @@ import android.text.TextUtils; /** * Used to change the background of lines where the span is attached to. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface LineBackgroundSpan extends ParagraphStyle { /** diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java index eeb638389271..1af1eed86e1f 100644 --- a/core/java/android/text/style/LineBreakConfigSpan.java +++ b/core/java/android/text/style/LineBreakConfigSpan.java @@ -31,6 +31,7 @@ import java.util.Objects; * LineBreakSpan for changing line break style of the specific region of the text. */ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class LineBreakConfigSpan implements ParcelableSpan { private final LineBreakConfig mLineBreakConfig; diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java index ae565d1c3317..71e8932c4aba 100644 --- a/core/java/android/text/style/LineHeightSpan.java +++ b/core/java/android/text/style/LineHeightSpan.java @@ -30,6 +30,7 @@ import com.android.internal.util.Preconditions; /** * The classes that affect the line height of paragraph should implement this interface. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { /** * Classes that implement this should define how the height is being calculated. diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java index 489ceeaa5542..be5525a2f41a 100644 --- a/core/java/android/text/style/LocaleSpan.java +++ b/core/java/android/text/style/LocaleSpan.java @@ -32,6 +32,7 @@ import java.util.Locale; /** * Changes the {@link Locale} of the text to which the span is attached. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LocaleSpan extends MetricAffectingSpan implements ParcelableSpan { @NonNull private final LocaleList mLocales; diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java index 587d1b4497dc..44db012953b3 100644 --- a/core/java/android/text/style/MaskFilterSpan.java +++ b/core/java/android/text/style/MaskFilterSpan.java @@ -30,6 +30,7 @@ import android.text.TextPaint; * <img src="{@docRoot}reference/android/images/text/style/maskfilterspan.png" /> * <figcaption>Text blurred with the <code>MaskFilterSpan</code>.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class MaskFilterSpan extends CharacterStyle implements UpdateAppearance { private MaskFilter mFilter; diff --git a/core/java/android/text/style/MetricAffectingSpan.java b/core/java/android/text/style/MetricAffectingSpan.java index 61b7947af638..f30fdc15ae39 100644 --- a/core/java/android/text/style/MetricAffectingSpan.java +++ b/core/java/android/text/style/MetricAffectingSpan.java @@ -23,6 +23,7 @@ import android.text.TextPaint; * The classes that affect character-level text formatting in a way that * changes the width or height of characters extend this class. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class MetricAffectingSpan extends CharacterStyle implements UpdateLayout { diff --git a/core/java/android/text/style/NoWritingToolsSpan.java b/core/java/android/text/style/NoWritingToolsSpan.java index 90f85aa69faa..c7dfcfa6dc0b 100644 --- a/core/java/android/text/style/NoWritingToolsSpan.java +++ b/core/java/android/text/style/NoWritingToolsSpan.java @@ -32,6 +32,7 @@ import android.text.TextUtils; * tools should only rewrite the user input text, and not modify the quoted text. */ @FlaggedApi(FLAG_WRITING_TOOLS) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class NoWritingToolsSpan implements ParcelableSpan { /** diff --git a/core/java/android/text/style/ParagraphStyle.java b/core/java/android/text/style/ParagraphStyle.java index 423156eca3de..27c1e261b116 100644 --- a/core/java/android/text/style/ParagraphStyle.java +++ b/core/java/android/text/style/ParagraphStyle.java @@ -20,6 +20,7 @@ package android.text.style; * The classes that affect paragraph-level text formatting implement * this interface. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface ParagraphStyle { diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java index 393ede653cb1..99c95749205a 100644 --- a/core/java/android/text/style/QuoteSpan.java +++ b/core/java/android/text/style/QuoteSpan.java @@ -57,6 +57,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/customquotespan.png" /> * <figcaption>Customized <code>QuoteSpan</code>.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class QuoteSpan implements LeadingMarginSpan, ParcelableSpan { /** * Default stripe width in pixels. diff --git a/core/java/android/text/style/RasterizerSpan.java b/core/java/android/text/style/RasterizerSpan.java index f0be50ab065c..cf8599c4f1b1 100644 --- a/core/java/android/text/style/RasterizerSpan.java +++ b/core/java/android/text/style/RasterizerSpan.java @@ -22,6 +22,7 @@ import android.text.TextPaint; /** * @removed Rasterizer is not supported for hw-accerlerated and PDF rendering */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class RasterizerSpan extends CharacterStyle implements UpdateAppearance { private Rasterizer mRasterizer; diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java index 5c91b201d28c..38d5d38ae704 100644 --- a/core/java/android/text/style/RelativeSizeSpan.java +++ b/core/java/android/text/style/RelativeSizeSpan.java @@ -34,6 +34,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/relativesizespan.png" /> * <figcaption>Text increased by 50% with <code>RelativeSizeSpan</code>.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableSpan { private final float mProportion; diff --git a/core/java/android/text/style/ReplacementSpan.java b/core/java/android/text/style/ReplacementSpan.java index 9430fd3a26c0..a6fe1fe72937 100644 --- a/core/java/android/text/style/ReplacementSpan.java +++ b/core/java/android/text/style/ReplacementSpan.java @@ -23,6 +23,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.text.TextPaint; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class ReplacementSpan extends MetricAffectingSpan { private CharSequence mContentDescription = null; diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java index d022b071b4d7..009973ee5306 100644 --- a/core/java/android/text/style/ScaleXSpan.java +++ b/core/java/android/text/style/ScaleXSpan.java @@ -36,6 +36,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/scalexspan.png" /> * <figcaption>Text scaled by 100% with <code>ScaleXSpan</code>.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan { private final float mProportion; diff --git a/core/java/android/text/style/SpanUtils.java b/core/java/android/text/style/SpanUtils.java index 6b4bd1a76358..21a96cdfe3b1 100644 --- a/core/java/android/text/style/SpanUtils.java +++ b/core/java/android/text/style/SpanUtils.java @@ -30,6 +30,7 @@ import java.util.List; /** * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SpanUtils { private SpanUtils() {} // Do not instantiate diff --git a/core/java/android/text/style/SpellCheckSpan.java b/core/java/android/text/style/SpellCheckSpan.java index e8ec3c6fb55c..39cd279d3d18 100644 --- a/core/java/android/text/style/SpellCheckSpan.java +++ b/core/java/android/text/style/SpellCheckSpan.java @@ -28,6 +28,7 @@ import android.text.TextUtils; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SpellCheckSpan implements ParcelableSpan { private boolean mSpellCheckInProgress; diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java index 65ee34717232..3654870ee088 100644 --- a/core/java/android/text/style/StrikethroughSpan.java +++ b/core/java/android/text/style/StrikethroughSpan.java @@ -32,6 +32,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/strikethroughspan.png" /> * <figcaption>Strikethrough text.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class StrikethroughSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java index 378682b9c890..c01e13443ad1 100644 --- a/core/java/android/text/style/StyleSpan.java +++ b/core/java/android/text/style/StyleSpan.java @@ -44,6 +44,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/stylespan.png" /> * <figcaption>Text styled bold and italic with the <code>StyleSpan</code>.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { private final int mStyle; diff --git a/core/java/android/text/style/SubscriptSpan.java b/core/java/android/text/style/SubscriptSpan.java index 729a9ad73e75..54c765d902a4 100644 --- a/core/java/android/text/style/SubscriptSpan.java +++ b/core/java/android/text/style/SubscriptSpan.java @@ -37,6 +37,7 @@ import android.text.TextUtils; * Note: Since the span affects the position of the text, if the text is on the last line of a * TextView, it may appear cut. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SubscriptSpan extends MetricAffectingSpan implements ParcelableSpan { /** diff --git a/core/java/android/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java index 1eee99aaac62..640fae4d1a3a 100644 --- a/core/java/android/text/style/SuggestionRangeSpan.java +++ b/core/java/android/text/style/SuggestionRangeSpan.java @@ -27,6 +27,7 @@ import android.text.TextUtils; * A SuggestionRangeSpan is used to show which part of an EditText is affected by a suggestion * popup window. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan { private int mBackgroundColor; diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index 0cf96f617f4a..d819062428f9 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -48,6 +48,7 @@ import java.util.Locale; * * @see TextView#isSuggestionsEnabled() */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { private static final String TAG = "SuggestionSpan"; diff --git a/core/java/android/text/style/SuperscriptSpan.java b/core/java/android/text/style/SuperscriptSpan.java index 561022352ffd..d3b339c02e92 100644 --- a/core/java/android/text/style/SuperscriptSpan.java +++ b/core/java/android/text/style/SuperscriptSpan.java @@ -35,6 +35,7 @@ import android.text.TextUtils; * TextView, it may appear cut. This can be avoided by decreasing the text size with an {@link * AbsoluteSizeSpan} */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SuperscriptSpan extends MetricAffectingSpan implements ParcelableSpan { /** * Creates a {@link SuperscriptSpan}. diff --git a/core/java/android/text/style/TabStopSpan.java b/core/java/android/text/style/TabStopSpan.java index 812847594ad8..e6733a2aac91 100644 --- a/core/java/android/text/style/TabStopSpan.java +++ b/core/java/android/text/style/TabStopSpan.java @@ -24,6 +24,7 @@ import android.annotation.Px; * the leading margin of the line. <code>TabStopSpan</code> will only affect the first tab * encountered on the first line of the text. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface TabStopSpan extends ParagraphStyle { /** diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java index 245a9dbc9f6c..7ede3499dc4d 100644 --- a/core/java/android/text/style/TextAppearanceSpan.java +++ b/core/java/android/text/style/TextAppearanceSpan.java @@ -58,6 +58,7 @@ import android.text.TextUtils; * @attr ref android.R.styleable#TextAppearance_fontVariationSettings * */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TextAppearanceSpan extends MetricAffectingSpan implements ParcelableSpan { private final String mFamilyName; private final int mStyle; diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java index e0d4ec1ca826..6d776d14fb00 100644 --- a/core/java/android/text/style/TtsSpan.java +++ b/core/java/android/text/style/TtsSpan.java @@ -42,6 +42,7 @@ import java.util.Locale; * The inner classes are there for convenience and provide builders for each * TtsSpan type. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TtsSpan implements ParcelableSpan { private final String mType; private final PersistableBundle mArgs; diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java index bdfc772c0328..86f7f7638629 100644 --- a/core/java/android/text/style/TypefaceSpan.java +++ b/core/java/android/text/style/TypefaceSpan.java @@ -50,6 +50,7 @@ import android.text.TextUtils; * <figcaption>Text with <code>TypefaceSpan</code>s constructed based on a font from resource and * from a font family.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan { @Nullable diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java index 9969d29a857d..f06627d0cbe1 100644 --- a/core/java/android/text/style/URLSpan.java +++ b/core/java/android/text/style/URLSpan.java @@ -41,6 +41,7 @@ import android.view.View; * <img src="{@docRoot}reference/android/images/text/style/urlspan.png" /> * <figcaption>Text with <code>URLSpan</code>.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class URLSpan extends ClickableSpan implements ParcelableSpan { private final String mURL; diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java index 075e70b7fbf5..b3bb142d1dc8 100644 --- a/core/java/android/text/style/UnderlineSpan.java +++ b/core/java/android/text/style/UnderlineSpan.java @@ -32,6 +32,7 @@ import android.text.TextUtils; * <img src="{@docRoot}reference/android/images/text/style/underlinespan.png" /> * <figcaption>Underlined text.</figcaption> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class UnderlineSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { diff --git a/core/java/android/text/style/UpdateAppearance.java b/core/java/android/text/style/UpdateAppearance.java index 7112347fcfbf..7b0a6d372122 100644 --- a/core/java/android/text/style/UpdateAppearance.java +++ b/core/java/android/text/style/UpdateAppearance.java @@ -22,5 +22,6 @@ package android.text.style; * that if the class also impacts size or other metrics, it should instead * implement {@link UpdateLayout}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface UpdateAppearance { } diff --git a/core/java/android/text/style/UpdateLayout.java b/core/java/android/text/style/UpdateLayout.java index 591075ecb972..5af4141cc8c2 100644 --- a/core/java/android/text/style/UpdateLayout.java +++ b/core/java/android/text/style/UpdateLayout.java @@ -22,4 +22,5 @@ package android.text.style; * this interface. This interface also includes {@link UpdateAppearance} * since such a change implicitly also impacts the appearance. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface UpdateLayout extends UpdateAppearance { } diff --git a/core/java/android/text/style/WrapTogetherSpan.java b/core/java/android/text/style/WrapTogetherSpan.java index 11721a8c3253..cf74c1bae3b7 100644 --- a/core/java/android/text/style/WrapTogetherSpan.java +++ b/core/java/android/text/style/WrapTogetherSpan.java @@ -16,6 +16,7 @@ package android.text.style; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface WrapTogetherSpan extends ParagraphStyle { diff --git a/core/java/android/text/util/Rfc822Token.java b/core/java/android/text/util/Rfc822Token.java index 2f207db9d494..d6e987b2952e 100644 --- a/core/java/android/text/util/Rfc822Token.java +++ b/core/java/android/text/util/Rfc822Token.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; * This class stores an RFC 822-like name, address, and comment, * and provides methods to convert them to quoted strings. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Rfc822Token { @Nullable private String mName, mAddress, mComment; diff --git a/core/java/android/text/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java index 68334e4d927c..8a9252ac9506 100644 --- a/core/java/android/text/util/Rfc822Tokenizer.java +++ b/core/java/android/text/util/Rfc822Tokenizer.java @@ -27,6 +27,7 @@ import java.util.Collection; * a string of addresses (such as might be typed into such a field) * into a series of Rfc822Tokens. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer { /** diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 231aa6816908..3f45e29f2d43 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -108,6 +108,7 @@ public final class Display { private final String mOwnerPackageName; private final Resources mResources; private DisplayAdjustments mDisplayAdjustments; + private boolean mRefreshRateChangesRegistered; @UnsupportedAppUsage private DisplayInfo mDisplayInfo; // never null @@ -1217,6 +1218,10 @@ public final class Display { */ public float getRefreshRate() { synchronized (mLock) { + if (!mRefreshRateChangesRegistered) { + DisplayManagerGlobal.getInstance().registerForRefreshRateChanges(); + mRefreshRateChangesRegistered = true; + } updateDisplayInfoLocked(); return mDisplayInfo.getRefreshRate(); } @@ -1601,7 +1606,7 @@ public final class Display { .INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED | DisplayManagerGlobal .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED, - ActivityThread.currentPackageName()); + ActivityThread.currentPackageName(), /* isEventFilterImplicit */ true); } } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index c174fbe0bbcd..3659e785f590 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1299,7 +1299,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // Handle the pending show request for other insets types since the IME insets // has being requested hidden. handlePendingControlRequest(statsToken); - getImeSourceConsumer().removeSurface(); + if (!Flags.refactorInsetsController()) { + // the surface can't be removed until the end of the animation. This is handled by + // IMMS after the window was requested to be hidden. + getImeSourceConsumer().removeSurface(); + } } applyAnimation(typesReady, false /* show */, fromIme, false /* skipsCallbacks */, statsToken); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 0903d2293d67..c4347f05f4a3 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3080,6 +3080,21 @@ public final class SurfaceControl implements Parcelable { } /** + * Changes the default ApplyToken. + * + * ApplyToken is used to determine the order in which Transactions are applied. + * Transactions applied with the same ApplyToken will be applied in the order + * they were queued in SurfaceFlinger. Transactions are sent via binder so the + * caller should be aware of the order in which binder calls are executed in + * SurfaceFlinger. This along with the ApplyToken will determine the order + * in which Transactions are applied. Transactions with different apply tokens + * will be applied in arbitrary order regardless of when they were queued in + * SurfaceFlinger. + * + * Caller must keep track of the previous ApplyToken if they want to restore it. + * + * Note each buffer producer should have its own ApplyToken in order to ensure + * that Transactions are not delayed by Transactions from other buffer producers. * * @hide */ @@ -3088,6 +3103,7 @@ public final class SurfaceControl implements Parcelable { } /** + * Returns the default ApplyToken. * * @hide */ @@ -3096,8 +3112,10 @@ public final class SurfaceControl implements Parcelable { } /** - * Apply the transaction, clearing it's state, and making it usable + * Apply the transaction, clearing its state, and making it usable * as a new transaction. + * + * This method will also increment the transaction ID for debugging purposes. */ public void apply() { apply(/*sync*/ false); @@ -3116,7 +3134,7 @@ public final class SurfaceControl implements Parcelable { /** - * Clear the transaction object, without applying it. + * Clear the transaction object, without applying it. The transction ID is preserved. * * @hide */ @@ -3375,6 +3393,9 @@ public final class SurfaceControl implements Parcelable { * If two siblings share the same Z order the ordering is undefined. Surfaces * with a negative Z will be placed below the parent surface. * + * Calling setLayer after setRelativeLayer will reset the relative layer + * in the same transaction. + * * @param sc The SurfaceControl to set the Z order on * @param z The Z-order * @return This Transaction. @@ -3392,6 +3413,22 @@ public final class SurfaceControl implements Parcelable { } /** + * Set the Z-order for a given SurfaceControl, relative to the specified SurfaceControl. + * The SurfaceControl with a negative z will be placed below the relativeTo + * SurfaceControl and the SurfaceControl with a positive z will be placed above the + * relativeTo SurfaceControl. + * + * Calling setLayer will reset the relative layer. Calling setRelativeLayer after setLayer + * will override the setLayer call. + * + * If a layer is set to be relative to a layer that is destroyed, the layer will be + * offscreen until setLayer is called or setRelativeLayer is called with a valid + * SurfaceControl. + * + * @param sc The SurfaceControl to set the Z order on + * @param relativeTo The SurfaceControl to set the Z order relative to + * @param z The Z-order + * @return This Transaction. * @hide */ public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) { @@ -3405,6 +3442,9 @@ public final class SurfaceControl implements Parcelable { } /** + * The hint from the buffer producer as to what portion of the layer is + * transparent. + * * @hide */ public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) { @@ -3438,6 +3478,10 @@ public final class SurfaceControl implements Parcelable { } /** + * Sets the input channel for a given SurfaceControl. The position and order of the + * SurfaceControl in conjunction with the touchable region in the InputWindowHandle + * determines the hit region. + * * @hide */ public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) { @@ -3549,6 +3593,8 @@ public final class SurfaceControl implements Parcelable { * surface. If no crop is specified and the surface has no buffer, the surface bounds is * only constrained by the size of its parent bounds. * + * To unset the crop, pass in an invalid Rect (0, 0, -1, -1) + * * @param sc SurfaceControl to set crop of. * @param crop Bounds of the crop to apply. * @hide @@ -3578,6 +3624,8 @@ public final class SurfaceControl implements Parcelable { * surface. If no crop is specified and the surface has no buffer, the surface bounds is * only constrained by the size of its parent bounds. * + * To unset the crop, pass in an invalid Rect (0, 0, -1, -1) + * * @param sc SurfaceControl to set crop of. * @param crop Bounds of the crop to apply. * @return this This transaction for chaining @@ -3625,6 +3673,8 @@ public final class SurfaceControl implements Parcelable { * surface. If no crop is specified and the surface has no buffer, the surface bounds is * only constrained by the size of its parent bounds. * + * To unset the crop, pass in an invalid Rect (0, 0, -1, -1) + * * @param sc SurfaceControl to set crop of. * @param crop Bounds of the crop to apply. * @return this This transaction for chaining @@ -3643,7 +3693,12 @@ public final class SurfaceControl implements Parcelable { } /** - * Sets the corner radius of a {@link SurfaceControl}. + * Sets the corner radius of a {@link SurfaceControl}. This corner radius is applied to the + * SurfaceControl and its children. The API expects a crop to be set on the SurfaceControl + * to ensure that the corner radius is applied to the correct region. If the crop does not + * intersect with the SurfaceControl's visible content, the corner radius will not be + * applied. + * * @param sc SurfaceControl * @param cornerRadius Corner radius in pixels. * @return Itself. @@ -3753,6 +3808,9 @@ public final class SurfaceControl implements Parcelable { } /** + * Associates a layer with a display. The layer will be drawn on the display with the + * specified layer stack. If the layer is not a root layer, this call has no effect. + * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) @@ -3791,6 +3849,7 @@ public final class SurfaceControl implements Parcelable { /** * Fills the surface with the specified color. + * * @param color A float array with three values to represent r, g, b in range [0..1]. An * invalid color will remove the color fill. * @hide @@ -3809,8 +3868,9 @@ public final class SurfaceControl implements Parcelable { /** * Removes color fill. - * @hide - */ + * + * @hide + */ public Transaction unsetColor(SurfaceControl sc) { checkPreconditions(sc); if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { @@ -3898,6 +3958,8 @@ public final class SurfaceControl implements Parcelable { } /** + * Sets the surface to render contents of the display to. + * * @hide */ public Transaction setDisplaySurface(IBinder displayToken, Surface surface) { @@ -3916,6 +3978,9 @@ public final class SurfaceControl implements Parcelable { } /** + * Sets the layer stack of the display. + * + * All layers with the same layer stack will be drawn on this display. * @hide */ public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) { diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index dd32947c69e4..b98f4db5dce6 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.flags.Flags.FLAG_DEPRECATE_SURFACE_VIEW_Z_ORDER_APIS; import static android.view.flags.Flags.FLAG_SURFACE_VIEW_GET_SURFACE_PACKAGE; import static android.view.flags.Flags.FLAG_SURFACE_VIEW_SET_COMPOSITION_ORDER; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; @@ -812,7 +813,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * window is attached to the window manager. * * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}. + * + * @deprecated Use {@link #setCompositionOrder(int)} instead. It provides more + * control over the Z ordering behavior. */ + @Deprecated + @FlaggedApi(FLAG_DEPRECATE_SURFACE_VIEW_Z_ORDER_APIS) public void setZOrderMediaOverlay(boolean isMediaOverlay) { mRequestedSubLayer = isMediaOverlay ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER; @@ -834,7 +840,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. * * @param onTop Whether to show the surface on top of this view's window. + * + * @deprecated Use {@link #setCompositionOrder(int)} instead. It provides more + * control over the Z ordering behavior. */ + @Deprecated + @FlaggedApi(FLAG_DEPRECATE_SURFACE_VIEW_Z_ORDER_APIS) public void setZOrderOnTop(boolean onTop) { // In R and above we allow dynamic layer changes. final boolean allowDynamicChange = getContext().getApplicationInfo().targetSdkVersion @@ -866,7 +877,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * @return Whether the Z ordering changed. * * @hide + * + * @deprecated Use {@link #setCompositionOrder(int)} instead. It provides more control + * over the Z ordering behavior. */ + @Deprecated public boolean setZOrderedOnTop(boolean onTop, boolean allowDynamicChange) { final int subLayer; if (onTop) { diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index e3ea6b229d64..a952967f4daf 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -1290,7 +1290,8 @@ public final class WindowInsets { int newTop = Math.max(0, insets.top - top); int newRight = Math.max(0, insets.right - right); int newBottom = Math.max(0, insets.bottom - bottom); - if (newLeft == left && newTop == top && newRight == right && newBottom == bottom) { + if (newLeft == insets.left && newTop == insets.top + && newRight == insets.right && newBottom == insets.bottom) { return insets; } return Insets.of(newLeft, newTop, newRight, newBottom); diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig index f6fdec94c332..d06f885638b6 100644 --- a/core/java/android/view/flags/view_flags.aconfig +++ b/core/java/android/view/flags/view_flags.aconfig @@ -129,6 +129,14 @@ flag { } flag { + name: "deprecate_surface_view_z_order_apis" + namespace: "window_surfaces" + description: "Deprecate SurfaceView z order control APIs." + bug: "341021569" + is_fixed_read_only: true +} + +flag { name: "use_refactored_round_scrollbar" namespace: "wear_frameworks" description: "Use refactored round scrollbar." diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index b44620f12bbb..4aeedbb72903 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -102,9 +102,15 @@ public enum DesktopModeFlags { ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true), ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true), ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS(Flags::enableModalsFullscreenWithPermission, false), + ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS( + Flags::enableOpaqueBackgroundForTransparentWindows, false), + ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX(Flags::enableQuickswitchDesktopSplitBugfix, true), ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true), ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE( Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true), + ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX( + Flags::enableStartLaunchTransitionFromTaskbarBugfix, true), + ENABLE_TASKBAR_OVERFLOW(Flags::enableTaskbarOverflow, false), ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true), ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true), ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true), @@ -117,6 +123,8 @@ public enum DesktopModeFlags { ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS( Flags::enableWindowingTransitionHandlersObservers, false), EXCLUDE_CAPTION_FROM_APP_BOUNDS(Flags::excludeCaptionFromAppBounds, false), + IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES( + Flags::ignoreAspectRatioRestrictionsForResizeableFreeformActivities, true), INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC( Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true) // go/keep-sorted end diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java index 61fc6226f822..d8d20119a602 100644 --- a/core/java/android/window/TransitionFilter.java +++ b/core/java/android/window/TransitionFilter.java @@ -31,8 +31,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.view.WindowManager; -import com.android.window.flags.Flags; - /** * A parcelable filter that can be used for rerouting transitions to a remote. This is a local * representation so that the transition system doesn't need to make blocking queries over @@ -261,9 +259,7 @@ public final class TransitionFilter implements Parcelable { // only applies to activity/task && (change.getTaskInfo() != null || change.getActivityComponent() != null)) { - final TransitionInfo.AnimationOptions opts = - Flags.moveAnimationOptionsToChange() ? change.getAnimationOptions() - : info.getAnimationOptions(); + final TransitionInfo.AnimationOptions opts = change.getAnimationOptions(); if (opts != null) { boolean canActuallyOverride = change.getTaskInfo() == null || opts.getOverrideTaskTransition(); diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 4f34aa36a204..32175f122d61 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -57,8 +57,6 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager; -import com.android.window.flags.Flags; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -220,10 +218,6 @@ public final class TransitionInfo implements Parcelable { private final ArrayList<Change> mChanges = new ArrayList<>(); private final ArrayList<Root> mRoots = new ArrayList<>(); - // TODO(b/327332488): Clean-up usages after the flag is fully enabled. - @Deprecated - private AnimationOptions mOptions; - /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */ private int mDebugId = -1; @@ -238,7 +232,6 @@ public final class TransitionInfo implements Parcelable { mFlags = in.readInt(); in.readTypedList(mChanges, Change.CREATOR); in.readTypedList(mRoots, Root.CREATOR); - mOptions = in.readTypedObject(AnimationOptions.CREATOR); mDebugId = in.readInt(); mTrack = in.readInt(); } @@ -250,7 +243,6 @@ public final class TransitionInfo implements Parcelable { dest.writeInt(mFlags); dest.writeTypedList(mChanges); dest.writeTypedList(mRoots, flags); - dest.writeTypedObject(mOptions, flags); dest.writeInt(mDebugId); dest.writeInt(mTrack); } @@ -286,18 +278,6 @@ public final class TransitionInfo implements Parcelable { mRoots.add(other); } - /** - * @deprecated Set {@link AnimationOptions} to change. This method is only used if - * {@link Flags#FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE} is disabled. - */ - @Deprecated - public void setAnimationOptions(@Nullable AnimationOptions options) { - if (Flags.moveAnimationOptionsToChange()) { - return; - } - mOptions = options; - } - public @TransitionType int getType() { return mType; } @@ -360,16 +340,6 @@ public final class TransitionInfo implements Parcelable { } /** - * @deprecated Use {@link Change#getAnimationOptions()} instead. This method is called only - * if {@link Flags#FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE} is disabled. - */ - @Deprecated - @Nullable - public AnimationOptions getAnimationOptions() { - return mOptions; - } - - /** * @return the list of {@link Change}s in this transition. The list is sorted top-to-bottom * in Z (meaning index 0 is the top-most container). */ @@ -455,9 +425,6 @@ public final class TransitionInfo implements Parcelable { StringBuilder sb = new StringBuilder(); sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType)) .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack); - if (mOptions != null) { - sb.append(" opt=").append(mOptions); - } sb.append(" r=["); for (int i = 0; i < mRoots.size(); ++i) { if (i > 0) { @@ -656,8 +623,6 @@ public final class TransitionInfo implements Parcelable { for (int i = 0; i < mRoots.size(); ++i) { out.mRoots.add(mRoots.get(i).localRemoteCopy()); } - // Doesn't have any native stuff, so no need for actual copy - out.mOptions = mOptions; return out; } @@ -860,9 +825,6 @@ public final class TransitionInfo implements Parcelable { * Sets {@link AnimationOptions} to override animation. */ public void setAnimationOptions(@Nullable AnimationOptions options) { - if (!Flags.moveAnimationOptionsToChange()) { - return; - } mAnimationOptions = options; } diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 3e3b8e100d6d..f77a36676b3d 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -681,6 +681,16 @@ flag { } flag { + name: "enable_opaque_background_for_transparent_windows" + namespace: "lse_desktop_experience" + description: "Whether an opaque background should be forcefully set for windows with only transparent background." + bug: "397219542" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_desktop_mode_through_dev_option" namespace: "lse_desktop_experience" description: "Enables support for desktop mode through developer options for devices eligible for desktop mode." @@ -705,6 +715,13 @@ flag { } flag { + name: "enable_activity_embedding_support_for_connected_displays" + namespace: "lse_desktop_experience" + description: "Enables activity embedding support for connected displays, including enabling AE optimization for Settings." + bug: "369438353" +} + +flag { name: "enable_full_screen_window_on_removing_split_screen_stage_bugfix" namespace: "lse_desktop_experience" description: "Enables clearing the windowing mode of a freeform window when removing the task from the split screen stage." @@ -764,3 +781,17 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_taskbar_overflow" + namespace: "lse_desktop_experience" + description: "Show recent apps in the taskbar overflow." + bug: "368119679" +} + +flag { + name: "enable_projected_display_desktop_mode" + namespace: "lse_desktop_experience" + description: "Enable Desktop Mode on Projected Mode devices but constrained to the external display." + bug: "384568161" +}
\ No newline at end of file diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 54d0eeffc9bb..6e45d3dc659f 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -62,16 +62,6 @@ flag { flag { namespace: "windowing_sdk" - name: "move_animation_options_to_change" - description: "Move AnimationOptions from TransitionInfo to each Change" - bug: "327332488" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - namespace: "windowing_sdk" name: "rear_display_disable_force_desktop_system_decorations" description: "Block system decorations from being added to a rear display when desktop mode is forced" bug: "346103150" diff --git a/core/java/com/android/internal/app/MediaRouteControllerContentManager.java b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java index dc4caa3d35d7..3a8b94f222ba 100644 --- a/core/java/com/android/internal/app/MediaRouteControllerContentManager.java +++ b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java @@ -17,7 +17,12 @@ package com.android.internal.app; import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.StateListDrawable; import android.media.MediaRouter; +import android.util.TypedValue; import android.view.View; import android.widget.LinearLayout; import android.widget.SeekBar; @@ -35,9 +40,14 @@ public class MediaRouteControllerContentManager { */ public interface Delegate { /** - * Updates the title of the cast device + * Updates the title of the media route device */ - void setCastDeviceTitle(CharSequence title); + void setMediaRouteDeviceTitle(CharSequence title); + + /** + * Updates the icon of the media route device + */ + void setMediaRouteDeviceIcon(Drawable icon); /** * Dismiss the UI to transition to a different workflow. @@ -45,6 +55,7 @@ public class MediaRouteControllerContentManager { void dismissView(); } + private final Context mContext; private final Delegate mDelegate; // Time to wait before updating the volume when the user lets go of the seek bar @@ -53,15 +64,25 @@ public class MediaRouteControllerContentManager { private static final int VOLUME_UPDATE_DELAY_MILLIS = 250; private final MediaRouter mRouter; + private final MediaRouteControllerContentManager.MediaRouterCallback mCallback; private final MediaRouter.RouteInfo mRoute; + private Drawable mMediaRouteButtonDrawable; + private final int[] mMediaRouteConnectingState = { R.attr.state_checked, R.attr.state_enabled }; + private final int[] mMediaRouteOnState = { R.attr.state_activated, R.attr.state_enabled }; + private Drawable mCurrentIconDrawable; + + private boolean mAttachedToWindow; + private LinearLayout mVolumeLayout; private SeekBar mVolumeSlider; private boolean mVolumeSliderTouched; public MediaRouteControllerContentManager(Context context, Delegate delegate) { + mContext = context; mDelegate = delegate; mRouter = context.getSystemService(MediaRouter.class); + mCallback = new MediaRouteControllerContentManager.MediaRouterCallback(); mRoute = mRouter.getSelectedRoute(); } @@ -70,7 +91,7 @@ public class MediaRouteControllerContentManager { * given container view. */ public void bindViews(View containerView) { - mDelegate.setCastDeviceTitle(mRoute.getName()); + mDelegate.setMediaRouteDeviceTitle(mRoute.getName()); mVolumeLayout = containerView.findViewById(R.id.media_route_volume_layout); mVolumeSlider = containerView.findViewById(R.id.media_route_volume_slider); mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @@ -108,20 +129,58 @@ public class MediaRouteControllerContentManager { } } }); + + mMediaRouteButtonDrawable = obtainMediaRouteButtonDrawable(); + } + + /** + * Called when this UI is attached to a window.. + */ + public void onAttachedToWindow() { + mAttachedToWindow = true; + mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS); + update(); + } + + /** + * Called when this UI is detached from a window.. + */ + public void onDetachedFromWindow() { + mRouter.removeCallback(mCallback); + mAttachedToWindow = false; } /** * Updates all the views to reflect new states. */ public void update() { - mDelegate.setCastDeviceTitle(mRoute.getName()); + if (!mRoute.isSelected() || mRoute.isDefault()) { + mDelegate.dismissView(); + } + + mDelegate.setMediaRouteDeviceTitle(mRoute.getName()); updateVolume(); + + Drawable icon = getIconDrawable(); + if (icon != mCurrentIconDrawable) { + mCurrentIconDrawable = icon; + if (icon instanceof AnimationDrawable animDrawable) { + if (!mAttachedToWindow && !mRoute.isConnecting()) { + // When the route is already connected before the view is attached, show the + // last frame of the connected animation immediately. + if (animDrawable.isRunning()) { + animDrawable.stop(); + } + icon = animDrawable.getFrame(animDrawable.getNumberOfFrames() - 1); + } else if (!animDrawable.isRunning()) { + animDrawable.start(); + } + } + mDelegate.setMediaRouteDeviceIcon(icon); + } } - /** - * Updates the volume layout and slider. - */ - public void updateVolume() { + private void updateVolume() { if (!mVolumeSliderTouched) { if (isVolumeControlAvailable()) { mVolumeLayout.setVisibility(View.VISIBLE); @@ -150,4 +209,61 @@ public class MediaRouteControllerContentManager { private boolean isVolumeControlAvailable() { return mRoute.getVolumeHandling() == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; } + + private Drawable obtainMediaRouteButtonDrawable() { + TypedValue value = new TypedValue(); + if (!mContext.getTheme().resolveAttribute(R.attr.mediaRouteButtonStyle, value, true)) { + return null; + } + int[] drawableAttrs = new int[] { R.attr.externalRouteEnabledDrawable }; + TypedArray a = mContext.obtainStyledAttributes(value.data, drawableAttrs); + Drawable drawable = a.getDrawable(0); + a.recycle(); + return drawable; + } + + private Drawable getIconDrawable() { + if (!(mMediaRouteButtonDrawable instanceof StateListDrawable)) { + return mMediaRouteButtonDrawable; + } else if (mRoute.isConnecting()) { + StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable; + stateListDrawable.setState(mMediaRouteConnectingState); + return stateListDrawable.getCurrent(); + } else { + StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable; + stateListDrawable.setState(mMediaRouteOnState); + return stateListDrawable.getCurrent(); + } + } + + private final class MediaRouterCallback extends MediaRouter.SimpleCallback { + @Override + public void onRouteUnselected(MediaRouter router, int type, MediaRouter.RouteInfo info) { + update(); + } + + @Override + public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { + update(); + } + + @Override + public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { + if (route == mRoute) { + updateVolume(); + } + } + + @Override + public void onRouteGrouped(MediaRouter router, MediaRouter.RouteInfo info, + MediaRouter.RouteGroup group, int index) { + update(); + } + + @Override + public void onRouteUngrouped(MediaRouter router, MediaRouter.RouteInfo info, + MediaRouter.RouteGroup group) { + update(); + } + } } diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java index 45a4a13667a4..5899963f4550 100644 --- a/core/java/com/android/internal/app/MediaRouteControllerDialog.java +++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java @@ -21,15 +21,9 @@ import android.app.MediaRouteActionProvider; import android.app.MediaRouteButton; import android.content.Context; import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.StateListDrawable; import android.media.MediaRouter; -import android.media.MediaRouter.RouteGroup; -import android.media.MediaRouter.RouteInfo; import android.os.Bundle; -import android.util.TypedValue; import android.view.KeyEvent; import android.view.View; @@ -48,19 +42,11 @@ import com.android.internal.R; */ public class MediaRouteControllerDialog extends AlertDialog implements MediaRouteControllerContentManager.Delegate { - // TODO(b/360050020): Eventually these 3 variables should be in the content manager instead of + // TODO(b/360050020): Eventually these 2 variables should be in the content manager instead of // here. So these should be removed when the migration is completed. private final MediaRouter mRouter; - private final MediaRouterCallback mCallback; private final MediaRouter.RouteInfo mRoute; - private Drawable mMediaRouteButtonDrawable; - private int[] mMediaRouteConnectingState = { R.attr.state_checked, R.attr.state_enabled }; - private int[] mMediaRouteOnState = { R.attr.state_activated, R.attr.state_enabled }; - private Drawable mCurrentIconDrawable; - - private boolean mAttachedToWindow; - private final MediaRouteControllerContentManager mContentManager; public MediaRouteControllerDialog(Context context, int theme) { @@ -68,7 +54,6 @@ public class MediaRouteControllerDialog extends AlertDialog implements mContentManager = new MediaRouteControllerContentManager(context, this); mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); - mCallback = new MediaRouterCallback(); mRoute = mRouter.getSelectedRoute(); } @@ -87,24 +72,18 @@ public class MediaRouteControllerDialog extends AlertDialog implements customPanelView.setMinimumHeight(0); } - mMediaRouteButtonDrawable = obtainMediaRouteButtonDrawable(); - update(); + mContentManager.update(); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); - mAttachedToWindow = true; - - mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS); - update(); + mContentManager.onAttachedToWindow(); } @Override public void onDetachedFromWindow() { - mRouter.removeCallback(mCallback); - mAttachedToWindow = false; - + mContentManager.onDetachedFromWindow(); super.onDetachedFromWindow(); } @@ -128,95 +107,17 @@ public class MediaRouteControllerDialog extends AlertDialog implements } @Override - public void setCastDeviceTitle(CharSequence title) { + public void setMediaRouteDeviceTitle(CharSequence title) { setTitle(title); } @Override - public void dismissView() { - dismiss(); - } - - private void update() { - if (!mRoute.isSelected() || mRoute.isDefault()) { - dismissView(); - } - - mContentManager.update(); - - Drawable icon = getIconDrawable(); - if (icon != mCurrentIconDrawable) { - mCurrentIconDrawable = icon; - if (icon instanceof AnimationDrawable animDrawable) { - if (!mAttachedToWindow && !mRoute.isConnecting()) { - // When the route is already connected before the view is attached, show the - // last frame of the connected animation immediately. - if (animDrawable.isRunning()) { - animDrawable.stop(); - } - icon = animDrawable.getFrame(animDrawable.getNumberOfFrames() - 1); - } else if (!animDrawable.isRunning()) { - animDrawable.start(); - } - } - setIcon(icon); - } - } - - private Drawable obtainMediaRouteButtonDrawable() { - Context context = getContext(); - TypedValue value = new TypedValue(); - if (!context.getTheme().resolveAttribute(R.attr.mediaRouteButtonStyle, value, true)) { - return null; - } - int[] drawableAttrs = new int[] { R.attr.externalRouteEnabledDrawable }; - TypedArray a = context.obtainStyledAttributes(value.data, drawableAttrs); - Drawable drawable = a.getDrawable(0); - a.recycle(); - return drawable; - } - - private Drawable getIconDrawable() { - if (!(mMediaRouteButtonDrawable instanceof StateListDrawable)) { - return mMediaRouteButtonDrawable; - } else if (mRoute.isConnecting()) { - StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable; - stateListDrawable.setState(mMediaRouteConnectingState); - return stateListDrawable.getCurrent(); - } else { - StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable; - stateListDrawable.setState(mMediaRouteOnState); - return stateListDrawable.getCurrent(); - } + public void setMediaRouteDeviceIcon(Drawable icon) { + setIcon(icon); } - private final class MediaRouterCallback extends MediaRouter.SimpleCallback { - @Override - public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { - update(); - } - - @Override - public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { - update(); - } - - @Override - public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { - if (route == mRoute) { - mContentManager.updateVolume(); - } - } - - @Override - public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, - int index) { - update(); - } - - @Override - public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { - update(); - } + @Override + public void dismissView() { + dismiss(); } } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 454323b60333..a0f45b082a36 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -257,15 +257,11 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai @Override public void surfaceDestroyed() { - - // Wait a while to give the system a chance for the remaining - // frames to arrive, then force finish the session. - mHandler.postDelayed(() -> { + mHandler.post(() -> { if (!mMetricsFinalized) { end(REASON_END_SURFACE_DESTROYED); - finish(); } - }, 50); + }); } }; // This callback has a reference to FrameTracker, @@ -367,9 +363,9 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai // Send a flush jank data transaction. if (mSurfaceControl != null && mSurfaceControl.isValid()) { SurfaceControl.Transaction.sendSurfaceFlushJankData(mSurfaceControl); - if (mJankDataListenerRegistration != null) { - mJankDataListenerRegistration.flush(); - } + } + if (mJankDataListenerRegistration != null) { + mJankDataListenerRegistration.flush(); } long delay; @@ -650,6 +646,8 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId + ", CUJ=" + name); } + } else if (Flags.useSfFrameDuration() && info.surfaceControlCallbackFired) { + maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos); } } maxSuccessiveMissedFramesCount = Math.max( diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index fe616e085488..5e9c87a5154a 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -16,6 +16,7 @@ package com.android.internal.os; +import android.annotation.CheckResult; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -196,7 +197,8 @@ public final class LongArrayMultiStateCounter implements Parcelable { /** * Populates the array with the accumulated counts for the specified state. */ - public void getCounts(long[] counts, int state) { + @CheckResult + public boolean getCounts(long[] counts, int state) { if (state < 0 || state >= mStateCount) { throw new IllegalArgumentException( "State: " + state + ", outside the range: [0-" + mStateCount + "]"); @@ -205,7 +207,7 @@ public final class LongArrayMultiStateCounter implements Parcelable { throw new IllegalArgumentException( "Invalid array length: " + counts.length + ", expected: " + mLength); } - native_getCounts(mNativeObject, counts, state); + return native_getCounts(mNativeObject, counts, state); } @Override @@ -282,7 +284,8 @@ public final class LongArrayMultiStateCounter implements Parcelable { @FastNative @RavenwoodRedirect - private static native void native_getCounts(long nativeObject, long[] counts, int state); + @CheckResult + private static native boolean native_getCounts(long nativeObject, long[] counts, int state); @FastNative @RavenwoodRedirect diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java index 4f5f37d0640e..403e8c1be8fa 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java @@ -168,8 +168,20 @@ class LongArrayMultiStateCounter_ravenwood { } } - public void getValues(long[] values, int state) { - System.arraycopy(mStates[state].mCounter, 0, values, 0, mArrayLength); + public boolean getValues(long[] values, int state) { + long[] counts = mStates[state].mCounter; + boolean allZeros = true; + for (int i = 0; i < counts.length; i++) { + if (counts[i] != 0) { + allZeros = false; + break; + } + } + if (allZeros) { + return false; + } + System.arraycopy(counts, 0, values, 0, mArrayLength); + return true; } public void reset() { @@ -316,8 +328,8 @@ class LongArrayMultiStateCounter_ravenwood { getInstance(instanceId).addCounts(counts); } - public static void native_getCounts(long instanceId, long[] counts, int state) { - getInstance(instanceId).getValues(counts, state); + public static boolean native_getCounts(long instanceId, long[] counts, int state) { + return getInstance(instanceId).getValues(counts, state); } public static void native_reset(long instanceId) { diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java index b57acf3d97fd..b19967a47001 100644 --- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java +++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java @@ -261,7 +261,12 @@ public class AconfigFlags { // Default value is false for when the flag is not found. // Note: Unlike with the old storage, with AconfigPackage, we don't have a way to // know if the flag is not found or if it's found but the value is false. - value = aconfigPackage.getBooleanFlagValue(flagName, false); + try { + value = aconfigPackage.getBooleanFlagValue(flagName, false); + } catch (Exception e) { + Slog.e(LOG_TAG, "Failed to read Aconfig flag value for " + flagPackageAndName, e); + return null; + } } if (DEBUG) { Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value); diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 641ecc9b675a..ce46da12aa76 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -250,7 +250,8 @@ public class ConversationLayout extends FrameLayout mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden); mPeopleHelper.animateViewForceHidden(mIcon, forceHidden); }); - mConversationText = findViewById(R.id.conversation_text); + mConversationText = findViewById(notificationsRedesignTemplates() + ? R.id.title : R.id.conversation_text); mExpandButtonContainer = findViewById(R.id.expand_button_container); mExpandButtonContainerA11yContainer = findViewById(R.id.expand_button_a11y_container); @@ -716,17 +717,10 @@ public class ConversationLayout extends FrameLayout } private void updateImageMessages() { - View newMessage = null; - if (mIsCollapsed && !mGroups.isEmpty()) { - - // When collapsed, we're displaying the image message in a dedicated container - // on the right of the layout instead of inline. Let's add the isolated image there - MessagingGroup messagingGroup = mGroups.getLast(); - MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage(); - if (isolatedMessage != null) { - newMessage = isolatedMessage.getView(); - } + if (mImageMessageContainer == null) { + return; } + View newMessage = getNewImageMessage(); // Remove all messages that don't belong into the image layout View previousMessage = mImageMessageContainer.getChildAt(0); if (previousMessage != newMessage) { @@ -738,6 +732,20 @@ public class ConversationLayout extends FrameLayout mImageMessageContainer.setVisibility(newMessage != null ? VISIBLE : GONE); } + @Nullable + private View getNewImageMessage() { + if (mIsCollapsed && !mGroups.isEmpty()) { + // When collapsed, we're displaying the image message in a dedicated container + // on the right of the layout instead of inline. Let's add the isolated image there + MessagingGroup messagingGroup = mGroups.getLast(); + MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + return isolatedMessage.getView(); + } + } + return null; + } + public void bindFacePile(ImageView bottomBackground, ImageView bottomView, ImageView topView) { applyNotificationBackgroundColor(bottomBackground); // Let's find the two last conversations: @@ -841,6 +849,10 @@ public class ConversationLayout extends FrameLayout } private void updateAppName() { + if (notificationsRedesignTemplates()) { + return; + } + mAppName.setVisibility(mIsCollapsed ? GONE : VISIBLE); } @@ -1533,6 +1545,10 @@ public class ConversationLayout extends FrameLayout } private void updateExpandButton() { + if (notificationsRedesignTemplates()) { + return; + } + int buttonGravity; ViewGroup newContainer; if (mIsCollapsed) { @@ -1565,6 +1581,10 @@ public class ConversationLayout extends FrameLayout } private void updateContentEndPaddings() { + if (notificationsRedesignTemplates()) { + return; + } + // Let's make sure the conversation header can't run into the expand button when we're // collapsed and update the paddings of the content int headerPaddingEnd; @@ -1593,6 +1613,10 @@ public class ConversationLayout extends FrameLayout } private void onAppNameVisibilityChanged() { + if (notificationsRedesignTemplates()) { + return; + } + boolean appNameGone = mAppName.getVisibility() == GONE; if (appNameGone != mAppNameGone) { mAppNameGone = appNameGone; @@ -1601,10 +1625,18 @@ public class ConversationLayout extends FrameLayout } private void updateAppNameDividerVisibility() { + if (notificationsRedesignTemplates()) { + return; + } + mAppNameDivider.setVisibility(mAppNameGone ? GONE : VISIBLE); } public void updateExpandability(boolean expandable, @Nullable OnClickListener onClickListener) { + if (notificationsRedesignTemplates()) { + return; + } + mExpandable = expandable; if (expandable) { mExpandButtonContainer.setVisibility(VISIBLE); diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 31d9770f6ac4..b9a603cc5696 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -449,12 +449,8 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements } private void updateIconVisibility() { - if (Flags.notificationsRedesignTemplates() && !mIsInConversation) { - // We don't show any icon (other than the app icon) in the collapsed form. For - // conversations, keeping this container helps with aligning the message to the icon - // when collapsed, but the old messaging style already has this alignment built into - // the template like all other layouts. Conversations are special because we use the - // same base layout for both the collapsed and expanded views. + if (Flags.notificationsRedesignTemplates()) { + // We don't show any icon (other than the app or person icon) in the collapsed form. mMessagingIconContainer.setVisibility(mSingleLine ? GONE : VISIBLE); } } diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index 64b44df8c982..eb22e7c8cdc0 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -337,19 +337,10 @@ public class MessagingLayout extends FrameLayout } private void updateImageMessages() { - View newMessage = null; if (mImageMessageContainer == null) { return; } - if (mIsCollapsed && !mGroups.isEmpty()) { - // When collapsed, we're displaying the image message in a dedicated container - // on the right of the layout instead of inline. Let's add the isolated image there - MessagingGroup messagingGroup = mGroups.getLast(); - MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage(); - if (isolatedMessage != null) { - newMessage = isolatedMessage.getView(); - } - } + View newMessage = getNewImageMessage(); // Remove all messages that don't belong into the image layout View previousMessage = mImageMessageContainer.getChildAt(0); if (previousMessage != newMessage) { @@ -368,6 +359,20 @@ public class MessagingLayout extends FrameLayout } } + @Nullable + private View getNewImageMessage() { + if (mIsCollapsed && !mGroups.isEmpty()) { + // When collapsed, we're displaying the image message in a dedicated container + // on the right of the layout instead of inline. Let's add the isolated image there + MessagingGroup messagingGroup = mGroups.getLast(); + MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + return isolatedMessage.getView(); + } + } + return null; + } + private void removeGroups(ArrayList<MessagingGroup> oldGroups) { int size = oldGroups.size(); for (int i = 0; i < size; i++) { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 06702e2fa4bf..92a841f9c290 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -76,6 +76,9 @@ cc_library_shared_for_libandroid_runtime { srcs: [ "android_animation_PropertyValuesHolder.cpp", "android_content_res_ApkAssets.cpp", + "android_media_ImageReader.cpp", + "android_media_PublicFormatUtils.cpp", + "android_media_Utils.cpp", "android_os_SystemClock.cpp", "android_os_SystemProperties.cpp", "android_text_AndroidCharacter.cpp", @@ -134,10 +137,7 @@ cc_library_shared_for_libandroid_runtime { "android_app_ActivityThread.cpp", "android_app_NativeActivity.cpp", "android_app_admin_SecurityLog.cpp", - "android_media_ImageReader.cpp", "android_media_ImageWriter.cpp", - "android_media_PublicFormatUtils.cpp", - "android_media_Utils.cpp", "android_opengl_EGL14.cpp", "android_opengl_EGL15.cpp", "android_opengl_EGLExt.cpp", @@ -489,6 +489,7 @@ cc_library_shared_for_libandroid_runtime { "libsqlite", "libgui_window_info_static", "libbinder", + "libbinder_ndk", "libhidlbase", // libhwbinder is in here ], version_script: "platform/linux/libandroid_runtime_export.txt", diff --git a/core/jni/android_app_PropertyInvalidatedCache.cpp b/core/jni/android_app_PropertyInvalidatedCache.cpp index 12585d5f8137..6267522f114f 100644 --- a/core/jni/android_app_PropertyInvalidatedCache.cpp +++ b/core/jni/android_app_PropertyInvalidatedCache.cpp @@ -35,7 +35,7 @@ int NonceStore::getMaxNonce() const { return kMaxNonce; } -size_t NonceStore::getMaxByte() const { +int32_t NonceStore::getMaxByte() const { return kMaxByte; } @@ -68,13 +68,13 @@ int32_t NonceStore::getHash() const { } // Copy the byte block to the target and return the current hash. -int32_t NonceStore::getByteBlock(block_t* block, size_t len) const { +int32_t NonceStore::getByteBlock(block_t* block, int32_t len) const { memcpy(block, (void*) byteBlock(), std::min(kMaxByte, len)); return mByteHash; } // Set the byte block and the hash. -void NonceStore::setByteBlock(int hash, const block_t* block, size_t len) { +void NonceStore::setByteBlock(int hash, const block_t* block, int32_t len) { memcpy((void*) byteBlock(), block, len = std::min(kMaxByte, len)); mByteHash = hash; } diff --git a/core/jni/android_app_PropertyInvalidatedCache.h b/core/jni/android_app_PropertyInvalidatedCache.h index 54a4ac65fce2..1d75182356c6 100644 --- a/core/jni/android_app_PropertyInvalidatedCache.h +++ b/core/jni/android_app_PropertyInvalidatedCache.h @@ -27,8 +27,12 @@ namespace android::app::PropertyInvalidatedCache { * location. Fields with a variable location are found via offsets. The offsets make this * object position-independent, which is required because it is in shared memory and would be * mapped into different virtual addresses for different processes. + * + * This structure is shared between 64-bit and 32-bit processes. Therefore it is imperative + * that the structure not use any datatypes that are architecture-dependent (like size_t). + * Additionally, care must be taken to avoid unexpected padding in the structure. */ -class NonceStore { +class alignas(8) NonceStore { protected: // A convenient typedef. The jbyteArray element type is jbyte, which the compiler treats as // signed char. @@ -43,23 +47,27 @@ class NonceStore { // The value of an unset field. static constexpr int UNSET = 0; - // The size of the nonce array. + // The size of the nonce array. This and the following sizes are int32_t to + // be ABI independent. const int32_t kMaxNonce; // The size of the byte array. - const size_t kMaxByte; + const int32_t kMaxByte; // The offset to the nonce array. - const size_t mNonceOffset; + const int32_t mNonceOffset; // The offset to the byte array. - const size_t mByteOffset; + const int32_t mByteOffset; // The byte block hash. This is fixed and at a known offset, so leave it in the base class. volatile std::atomic<int32_t> mByteHash; + // A 4-byte padd that makes the size of this structure a multiple of 8 bytes. + const int32_t _pad = 0; + // The constructor is protected! It only makes sense when called from a subclass. - NonceStore(int kMaxNonce, size_t kMaxByte, volatile nonce_t* nonce, volatile block_t* block) : + NonceStore(int kMaxNonce, int kMaxByte, volatile nonce_t* nonce, volatile block_t* block) : kMaxNonce(kMaxNonce), kMaxByte(kMaxByte), mNonceOffset(offset(this, const_cast<nonce_t*>(nonce))), @@ -70,7 +78,7 @@ class NonceStore { // These provide run-time access to the sizing parameters. int getMaxNonce() const; - size_t getMaxByte() const; + int getMaxByte() const; // Fetch a nonce, returning UNSET if the index is out of range. This method specifically // does not throw or generate an error if the index is out of range; this allows the method @@ -86,10 +94,10 @@ class NonceStore { int32_t getHash() const; // Copy the byte block to the target and return the current hash. - int32_t getByteBlock(block_t* block, size_t len) const; + int32_t getByteBlock(block_t* block, int32_t len) const; // Set the byte block and the hash. - void setByteBlock(int hash, const block_t* block, size_t len); + void setByteBlock(int hash, const block_t* block, int32_t len); private: @@ -113,6 +121,12 @@ class NonceStore { } }; +// Assert that the size of the object is fixed, independent of the CPU architecture. There are +// four int32_t fields and one atomic<int32_t>, which sums to 20 bytes total. This assertion +// uses a constant instead of computing the size of the objects in the compiler, to avoid +// different answers on different architectures. +static_assert(sizeof(NonceStore) == 24); + /** * A cache nonce block contains an array of std::atomic<int64_t> and an array of bytes. The * byte array has an associated hash. This class provides methods to read and write the fields @@ -126,20 +140,22 @@ class NonceStore { * The template is parameterized by the number of nonces it supports and the number of bytes in * the string block. */ -template<int maxNonce, size_t maxByte> class CacheNonce : public NonceStore { +template<int MAX_NONCE, int MAX_BYTE> class CacheNonce : public NonceStore { // The array of nonces - volatile nonce_t mNonce[maxNonce]; + volatile nonce_t mNonce[MAX_NONCE]; // The byte array. This is not atomic but it is guarded by the mByteHash. - volatile block_t mByteBlock[maxByte]; + volatile block_t mByteBlock[MAX_BYTE]; public: + // Export the parameters for use in compiler assertions. + static constexpr int kMaxNonceCount = MAX_NONCE; + static constexpr int kMaxByteCount = MAX_BYTE; + // Construct and initialize the memory. - CacheNonce() : - NonceStore(maxNonce, maxByte, &mNonce[0], &mByteBlock[0]) - { - for (int i = 0; i < maxNonce; i++) { + CacheNonce() : NonceStore(MAX_NONCE, MAX_BYTE, &mNonce[0], &mByteBlock[0]) { + for (int i = 0; i < MAX_NONCE; i++) { mNonce[i] = UNSET; } mByteHash = UNSET; @@ -155,4 +171,10 @@ template<int maxNonce, size_t maxByte> class CacheNonce : public NonceStore { typedef CacheNonce</* max nonce */ 128, /* byte block size */ 8192> SystemCacheNonce; // LINT.ThenChange(/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java:system_nonce_config) +// Verify that there is no padding in the final class. +static_assert(sizeof(SystemCacheNonce) == + sizeof(NonceStore) + + SystemCacheNonce::kMaxNonceCount*8 + + SystemCacheNonce::kMaxByteCount); + } // namespace android.app.PropertyInvalidatedCache diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index a52678359423..1a7490e61873 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -26,6 +26,7 @@ #include <nativehelper/JNIHelp.h> #include <utils/Log.h> #include <utils/RefBase.h> +#include <utils/StrongPointer.h> #include "core_jni_helpers.h" @@ -124,7 +125,7 @@ private: static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jboolean updateDestinationFrame) { ScopedUtfChars name(env, jName); - sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name.c_str(), updateDestinationFrame); + sp<BLASTBufferQueue> queue = sp<BLASTBufferQueue>::make(name.c_str(), updateDestinationFrame); queue->incStrong((void*)nativeCreate); return reinterpret_cast<jlong>(queue.get()); } diff --git a/core/jni/android_media_ImageReader.cpp b/core/jni/android_media_ImageReader.cpp index 20b9c571317e..a34cb391c2fd 100644 --- a/core/jni/android_media_ImageReader.cpp +++ b/core/jni/android_media_ImageReader.cpp @@ -25,13 +25,22 @@ #include <android_runtime/android_view_Surface.h> #include <com_android_graphics_libgui_flags.h> #include <cutils/atomic.h> +#ifdef __ANDROID__ #include <grallocusage/GrallocUsageConversion.h> +#else +#define GRALLOC_USAGE_PROTECTED 0 +#define GRALLOC_USAGE_SW_READ_OFTEN 0 +#define GRALLOC_USAGE_SW_WRITE_OFTEN 0 +#endif #include <gui/BufferItemConsumer.h> #include <gui/Surface.h> #include <inttypes.h> #include <jni.h> +#include <jni_wrappers.h> #include <nativehelper/JNIHelp.h> +#ifdef __ANDROID__ #include <private/android/AHardwareBufferHelpers.h> +#endif #include <stdint.h> #include <ui/Rect.h> #include <utils/List.h> @@ -393,8 +402,12 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint w String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d", width, height, nativeHalFormat, maxImages, getpid(), createProcessUniqueId()); +#ifdef __ANDROID__ uint64_t consumerUsage = android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage); +#else + uint64_t consumerUsage = 0; +#endif #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) sp<BufferItemConsumer> bufferConsumer = new BufferItemConsumer(consumerUsage, maxImages, @@ -773,6 +786,7 @@ static bool Image_getLockedImageInfo(JNIEnv* env, LockedImage* buffer, int idx, return true; } +#ifdef __ANDROID__ static void ImageReader_unlockGraphicBuffer(JNIEnv* env, jobject /*thiz*/, jobject buffer) { sp<GraphicBuffer> graphicBuffer = @@ -856,6 +870,7 @@ static jobjectArray ImageReader_createImagePlanes(JNIEnv* env, jobject /*thiz*/, return imagePlanes; } +#endif static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz, int numPlanes, int halReaderFormat, uint64_t ndkReaderUsage) @@ -964,6 +979,7 @@ static jint Image_getFormat(JNIEnv* env, jobject thiz, jint readerFormat) } } +#ifdef __ANDROID__ static jobject Image_getHardwareBuffer(JNIEnv* env, jobject thiz) { BufferItem* buffer = Image_getBufferItem(env, thiz); if (buffer == nullptr) { @@ -976,45 +992,48 @@ static jobject Image_getHardwareBuffer(JNIEnv* env, jobject thiz) { // to link against libandroid.so return android_hardware_HardwareBuffer_createFromAHardwareBuffer(env, b); } +#endif } // extern "C" // ---------------------------------------------------------------------------- -static const JNINativeMethod gImageReaderMethods[] = { - {"nativeClassInit", "()V", (void*)ImageReader_classInit }, - {"nativeInit", "(Ljava/lang/Object;IIIJII)V", (void*)ImageReader_init }, - {"nativeClose", "()V", (void*)ImageReader_close }, - {"nativeReleaseImage", "(Landroid/media/Image;)V", (void*)ImageReader_imageRelease }, - {"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup }, - {"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface }, - {"nativeDetachImage", "(Landroid/media/Image;Z)I", (void*)ImageReader_detachImage }, - {"nativeCreateImagePlanes", - "(ILandroid/graphics/GraphicBuffer;IIIIII)[Landroid/media/ImageReader$ImagePlane;", - (void*)ImageReader_createImagePlanes }, - {"nativeUnlockGraphicBuffer", - "(Landroid/graphics/GraphicBuffer;)V", (void*)ImageReader_unlockGraphicBuffer }, - {"nativeDiscardFreeBuffers", "()V", (void*)ImageReader_discardFreeBuffers } -}; +static const JNINativeMethod gImageReaderMethods[] = + {{"nativeClassInit", "()V", (void*)ImageReader_classInit}, + {"nativeInit", "(Ljava/lang/Object;IIIJII)V", (void*)ImageReader_init}, + {"nativeClose", "()V", (void*)ImageReader_close}, + {"nativeReleaseImage", "(Landroid/media/Image;)V", (void*)ImageReader_imageRelease}, + {"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup}, + {"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface}, + {"nativeDetachImage", "(Landroid/media/Image;Z)I", (void*)ImageReader_detachImage}, +#ifdef __ANDROID__ + {"nativeCreateImagePlanes", + "(ILandroid/graphics/GraphicBuffer;IIIIII)[Landroid/media/ImageReader$ImagePlane;", + (void*)ImageReader_createImagePlanes}, + {"nativeUnlockGraphicBuffer", "(Landroid/graphics/GraphicBuffer;)V", + (void*)ImageReader_unlockGraphicBuffer}, +#endif + {"nativeDiscardFreeBuffers", "()V", (void*)ImageReader_discardFreeBuffers}}; static const JNINativeMethod gImageMethods[] = { - {"nativeCreatePlanes", "(IIJ)[Landroid/media/ImageReader$SurfaceImage$SurfacePlane;", - (void*)Image_createSurfacePlanes }, - {"nativeGetWidth", "()I", (void*)Image_getWidth }, - {"nativeGetHeight", "()I", (void*)Image_getHeight }, - {"nativeGetFormat", "(I)I", (void*)Image_getFormat }, - {"nativeGetFenceFd", "()I", (void*)Image_getFenceFd }, - {"nativeGetHardwareBuffer", "()Landroid/hardware/HardwareBuffer;", - (void*)Image_getHardwareBuffer }, + {"nativeCreatePlanes", "(IIJ)[Landroid/media/ImageReader$SurfaceImage$SurfacePlane;", + (void*)Image_createSurfacePlanes}, + {"nativeGetWidth", "()I", (void*)Image_getWidth}, + {"nativeGetHeight", "()I", (void*)Image_getHeight}, + {"nativeGetFormat", "(I)I", (void*)Image_getFormat}, + {"nativeGetFenceFd", "()I", (void*)Image_getFenceFd}, +#ifdef __ANDROID__ + {"nativeGetHardwareBuffer", "()Landroid/hardware/HardwareBuffer;", + (void*)Image_getHardwareBuffer}, +#endif }; int register_android_media_ImageReader(JNIEnv *env) { + int ret1 = RegisterMethodsOrDie(env, "android/media/ImageReader", gImageReaderMethods, + NELEM(gImageReaderMethods)); - int ret1 = AndroidRuntime::registerNativeMethods(env, - "android/media/ImageReader", gImageReaderMethods, NELEM(gImageReaderMethods)); - - int ret2 = AndroidRuntime::registerNativeMethods(env, - "android/media/ImageReader$SurfaceImage", gImageMethods, NELEM(gImageMethods)); + int ret2 = RegisterMethodsOrDie(env, "android/media/ImageReader$SurfaceImage", gImageMethods, + NELEM(gImageMethods)); return (ret1 || ret2); } diff --git a/core/jni/android_media_PublicFormatUtils.cpp b/core/jni/android_media_PublicFormatUtils.cpp index 04494ad00a65..bcdf654d5a8c 100644 --- a/core/jni/android_media_PublicFormatUtils.cpp +++ b/core/jni/android_media_PublicFormatUtils.cpp @@ -16,10 +16,10 @@ #define LOG_TAG "PublicFormatUtils_JNI" -#include <utils/misc.h> -#include <ui/PublicFormat.h> -#include <android_runtime/AndroidRuntime.h> #include <jni.h> +#include <jni_wrappers.h> +#include <ui/PublicFormat.h> +#include <utils/misc.h> using namespace android; @@ -53,7 +53,6 @@ static const JNINativeMethod gMethods[] = { }; int register_android_media_PublicFormatUtils(JNIEnv *env) { - return AndroidRuntime::registerNativeMethods(env, - "android/media/PublicFormatUtils", gMethods, NELEM(gMethods)); + return RegisterMethodsOrDie(env, "android/media/PublicFormatUtils", gMethods, NELEM(gMethods)); } diff --git a/core/jni/android_media_Utils.cpp b/core/jni/android_media_Utils.cpp index e8f8644a4503..e48414113cf0 100644 --- a/core/jni/android_media_Utils.cpp +++ b/core/jni/android_media_Utils.cpp @@ -19,10 +19,12 @@ #include "android_media_Utils.h" +#ifdef __ANDROID__ // Layoutlib does not support hardware #include <aidl/android/hardware/graphics/common/PixelFormat.h> #include <aidl/android/hardware/graphics/common/PlaneLayoutComponentType.h> #include <ui/GraphicBufferMapper.h> #include <ui/GraphicTypes.h> +#endif #include <utils/Log.h> #define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) ) @@ -34,7 +36,13 @@ namespace android { // -----------Utility functions used by ImageReader/Writer JNI----------------- +#ifdef __ANDROID__ using AidlPixelFormat = aidl::android::hardware::graphics::common::PixelFormat; +#else +namespace AidlPixelFormat { +const int32_t YCBCR_P210 = 60; +} +#endif enum { IMAGE_MAX_NUM_PLANES = 3, @@ -517,6 +525,7 @@ status_t getLockedImageInfo(LockedImage* buffer, int idx, return OK; } +#ifdef __ANDROID__ static status_t extractP010Gralloc4PlaneLayout( sp<GraphicBuffer> buffer, void *pData, int format, LockedImage *outputImage) { using aidl::android::hardware::graphics::common::PlaneLayoutComponent; @@ -663,6 +672,7 @@ static status_t extractP210Gralloc4PlaneLayout(sp<GraphicBuffer> buffer, void *p outputImage->chromaStep = 4; return OK; } +#endif status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage, const Rect& rect, int fenceFd, LockedImage* outputImage) { @@ -701,6 +711,7 @@ status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage, ALOGE("Lock buffer failed!"); return res; } +#ifdef __ANDROID__ if (isPossibly10BitYUV(format)) { if (format == HAL_PIXEL_FORMAT_YCBCR_P010 && OK == extractP010Gralloc4PlaneLayout(buffer, pData, format, outputImage)) { @@ -713,6 +724,7 @@ status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage, return OK; } } +#endif } outputImage->data = reinterpret_cast<uint8_t*>(pData); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index b99b0ef7f24e..a8e51a7c1fe8 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -765,28 +765,28 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong std::vector<int32_t> dimensions; std::vector<int32_t> sizes; std::vector<int32_t> samplingKeys; - int32_t fd = -1; + base::unique_fd fd; if (jdimensionArray) { jsize numLuts = env->GetArrayLength(jdimensionArray); - ScopedIntArrayRW joffsets(env, joffsetArray); + ScopedIntArrayRO joffsets(env, joffsetArray); if (joffsets.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray"); + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from joffsetArray"); return; } - ScopedIntArrayRW jdimensions(env, jdimensionArray); + ScopedIntArrayRO jdimensions(env, jdimensionArray); if (jdimensions.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray"); + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jdimensionArray"); return; } - ScopedIntArrayRW jsizes(env, jsizeArray); + ScopedIntArrayRO jsizes(env, jsizeArray); if (jsizes.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray"); + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsizeArray"); return; } - ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray); + ScopedIntArrayRO jsamplingKeys(env, jsamplingKeyArray); if (jsamplingKeys.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray"); + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsamplingKeyArray"); return; } @@ -796,15 +796,15 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong sizes = std::vector<int32_t>(jsizes.get(), jsizes.get() + numLuts); samplingKeys = std::vector<int32_t>(jsamplingKeys.get(), jsamplingKeys.get() + numLuts); - ScopedFloatArrayRW jbuffers(env, jbufferArray); + ScopedFloatArrayRO jbuffers(env, jbufferArray); if (jbuffers.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray"); + jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRO from jbufferArray"); return; } // create the shared memory and copy jbuffers size_t bufferSize = jbuffers.size() * sizeof(float); - fd = ashmem_create_region("lut_shared_mem", bufferSize); + fd.reset(ashmem_create_region("lut_shared_mem", bufferSize)); if (fd < 0) { jniThrowRuntimeException(env, "ashmem_create_region() failed"); return; @@ -820,7 +820,7 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong } } - transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys); + transaction->setLuts(ctrl, std::move(fd), offsets, dimensions, sizes, samplingKeys); } static void nativeSetPictureProfileId(JNIEnv* env, jclass clazz, jlong transactionObj, diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp index 8f36ecb8b01a..7d94ef85f51a 100644 --- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -116,17 +116,26 @@ static void native_reset(jlong nativePtr) { counter->reset(); } -static void native_getCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jint state) { +static bool native_getCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jint state) { auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); - ScopedLongArrayRW scopedArray(env, values); auto *data = counter->getCount(state).data(); - auto size = env->GetArrayLength(values); - auto *outData = scopedArray.get(); if (data == nullptr) { - memset(outData, 0, size * sizeof(uint64_t)); - } else { - memcpy(outData, data, size * sizeof(uint64_t)); + return false; + } + auto size = env->GetArrayLength(values); + bool allZeros = true; + for (int i = 0; i < size; i++) { + if (data[i]) { + allZeros = false; + break; + } } + if (allZeros) { + return false; + } + ScopedLongArrayRW scopedArray(env, values); + memcpy(scopedArray.get(), data, size * sizeof(uint64_t)); + return true; } static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) { @@ -255,7 +264,7 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = { // @CriticalNative {"native_reset", "(J)V", (void *)native_reset}, // @FastNative - {"native_getCounts", "(J[JI)V", (void *)native_getCounts}, + {"native_getCounts", "(J[JI)Z", (void *)native_getCounts}, // @FastNative {"native_toString", "(J)Ljava/lang/String;", (void *)native_toString}, // @FastNative diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h index e3e17eed54d5..1f44994b54f2 100644 --- a/core/jni/jni_wrappers.h +++ b/core/jni/jni_wrappers.h @@ -22,6 +22,8 @@ #include <log/log.h> #include <nativehelper/JNIHelp.h> +#include <string> + namespace android { static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) { diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp index 1a0328338980..746740b0248b 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -48,6 +48,8 @@ using namespace std; * (see AndroidRuntime.cpp). */ +extern int register_android_media_ImageReader(JNIEnv* env); +extern int register_android_media_PublicFormatUtils(JNIEnv* env); extern int register_android_os_Binder(JNIEnv* env); extern int register_libcore_util_NativeAllocationRegistry(JNIEnv* env); @@ -126,6 +128,10 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)}, {"android.database.sqlite.SQLiteRawStatement", REG_JNI(register_android_database_SQLiteRawStatement)}, +#endif + {"android.media.ImageReader", REG_JNI(register_android_media_ImageReader)}, + {"android.media.PublicFormatUtils", REG_JNI(register_android_media_PublicFormatUtils)}, +#ifdef __linux__ {"android.os.Binder", REG_JNI(register_android_os_Binder)}, {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)}, {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 78526ad4a06b..ee6899cf866b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7759,7 +7759,17 @@ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @hide --> <permission android:name="android.permission.READ_BLOCKED_NUMBERS" - android:protectionLevel="signature" /> + android:protectionLevel="signature" + android:featureFlag="!android.permission.flags.grant_read_blocked_numbers_to_system_ui_intelligence" /> + + <!-- Allows the holder to read blocked numbers. See + {@link android.provider.BlockedNumberContract}. + @SystemApi + @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") + @hide --> + <permission android:name="android.permission.READ_BLOCKED_NUMBERS" + android:protectionLevel="signature|role" + android:featureFlag="android.permission.flags.grant_read_blocked_numbers_to_system_ui_intelligence" /> <!-- Allows the holder to write blocked numbers. See {@link android.provider.BlockedNumberContract}. diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml index 1bde17358825..68096f8cc50e 100644 --- a/core/res/res/layout/notification_2025_conversation_header.xml +++ b/core/res/res/layout/notification_2025_conversation_header.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2024 The Android Open Source Project + ~ 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. @@ -15,157 +15,74 @@ ~ limitations under the License --> -<com.android.internal.widget.ConversationHeaderLinearLayout +<!-- extends RelativeLayout --> +<NotificationHeaderView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/conversation_header" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:id="@+id/notification_header" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_2025_header_height" + android:clipChildren="false" + android:gravity="center_vertical" android:orientation="horizontal" - android:paddingTop="@dimen/notification_2025_margin" + android:theme="@style/Theme.DeviceDefault.Notification" + android:importantForAccessibility="no" > - <TextView - android:id="@+id/conversation_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" - android:textSize="16sp" - android:singleLine="true" - android:layout_weight="1" - /> - - <TextView - android:id="@+id/app_name_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" - android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" - android:text="@string/notification_header_divider_symbol" - android:singleLine="true" + <ImageView + android:id="@+id/left_icon" + android:layout_width="@dimen/notification_2025_left_icon_size" + android:layout_height="@dimen/notification_2025_left_icon_size" + android:layout_alignParentStart="true" + android:layout_margin="@dimen/notification_2025_margin" + android:background="@drawable/notification_large_icon_outline" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:scaleType="centerCrop" android:visibility="gone" /> - <!-- App Name --> - <com.android.internal.widget.ObservableTextView - android:id="@+id/app_name_text" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" - android:singleLine="true" - android:visibility="gone" - /> + <include layout="@layout/notification_2025_conversation_icon_container" /> - <TextView - android:id="@+id/time_divider" + <!-- extends ViewGroup --> + <NotificationTopLineView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/notification_top_line" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" - android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" - android:text="@string/notification_header_divider_symbol" - android:singleLine="true" - android:visibility="gone" - /> + android:layout_alignParentStart="true" + android:layout_toStartOf="@id/expand_button" + android:layout_alignWithParentIfMissing="true" + android:layout_marginVertical="@dimen/notification_2025_margin" + android:clipChildren="false" + android:gravity="center_vertical" + android:paddingStart="@dimen/notification_2025_content_margin_start" + android:theme="@style/Theme.DeviceDefault.Notification" + > - <DateTimeView - android:id="@+id/time" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" - android:showRelative="true" - android:singleLine="true" - android:visibility="gone" - /> + <include layout="@layout/notification_2025_top_line_views" /> - <ViewStub - android:id="@+id/chronometer" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" - android:layout="@layout/notification_template_part_chronometer" - android:visibility="gone" - /> + </NotificationTopLineView> - <TextView - android:id="@+id/verification_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" - android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" - android:text="@string/notification_header_divider_symbol" - android:singleLine="true" - android:visibility="gone" - /> - - <ImageView - android:id="@+id/verification_icon" - android:layout_width="@dimen/notification_verification_icon_size" - android:layout_height="@dimen/notification_verification_icon_size" - android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" - android:baseline="10dp" - android:scaleType="fitCenter" - android:src="@drawable/ic_notifications_alerted" - android:visibility="gone" + <FrameLayout + android:id="@+id/alternate_expand_target" + android:layout_width="@dimen/notification_2025_content_margin_start" + android:layout_height="match_parent" + android:layout_alignParentStart="true" + android:importantForAccessibility="no" + android:focusable="false" /> - <TextView - android:id="@+id/verification_text" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + <include layout="@layout/notification_2025_expand_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" - android:layout_weight="100" - android:showRelative="true" - android:singleLine="true" - android:visibility="gone" - /> + android:layout_gravity="top|end" + android:layout_alignParentEnd="true" /> - <ImageButton - android:id="@+id/feedback" - android:layout_width="@dimen/notification_feedback_size" - android:layout_height="@dimen/notification_feedback_size" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:background="?android:selectableItemBackgroundBorderless" - android:contentDescription="@string/notification_feedback_indicator" - android:baseline="13dp" - android:scaleType="fitCenter" - android:src="@drawable/ic_feedback_indicator" - android:visibility="gone" - /> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_alignParentTop="true" + android:layout_alignParentEnd="true" /> - <ImageView - android:id="@+id/phishing_alert" - android:layout_width="@dimen/notification_2025_badge_size" - android:layout_height="@dimen/notification_2025_badge_size" - android:layout_marginStart="@dimen/notification_2025_badge_margin" - android:baseline="@dimen/notification_2025_badge_baseline" - android:scaleType="fitCenter" - android:src="@drawable/ic_dialog_alert_material" - android:visibility="gone" - android:contentDescription="@string/notification_phishing_alert_content_description" - /> - - <ImageView - android:id="@+id/profile_badge" - android:layout_width="@dimen/notification_2025_badge_size" - android:layout_height="@dimen/notification_2025_badge_size" - android:layout_marginStart="@dimen/notification_2025_badge_margin" - android:baseline="@dimen/notification_2025_badge_baseline" - android:scaleType="fitCenter" - android:visibility="gone" - android:contentDescription="@string/notification_work_profile_content_description" - /> - - <ImageView - android:id="@+id/alerted_icon" - android:layout_width="@dimen/notification_2025_badge_size" - android:layout_height="@dimen/notification_2025_badge_size" - android:layout_marginStart="@dimen/notification_2025_badge_margin" - android:baseline="@dimen/notification_2025_badge_baseline" - android:contentDescription="@string/notification_alerted_content_description" - android:scaleType="fitCenter" - android:src="@drawable/ic_notifications_alerted" - android:visibility="gone" - /> -</com.android.internal.widget.ConversationHeaderLinearLayout> +</NotificationHeaderView> diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml index 6f3c15adb082..ee691e4d6894 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_call.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2024 The Android Open Source Project + ~ 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. @@ -25,55 +25,177 @@ android:theme="@style/Theme.DeviceDefault.Notification" > - <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts --> - <include layout="@layout/notification_2025_conversation_icon_container" /> - <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/notification_2025_min_height" - android:orientation="horizontal" + android:clipChildren="false" + android:orientation="vertical" > - <LinearLayout - android:id="@+id/notification_main_column" + <com.android.internal.widget.NotificationMaxHeightFrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:orientation="vertical" - android:paddingBottom="@dimen/notification_2025_margin" + android:minHeight="@dimen/notification_2025_min_height" + android:clipChildren="false" > - <include - layout="@layout/notification_2025_conversation_header" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + <ImageView + android:id="@+id/left_icon" + android:layout_width="@dimen/notification_2025_left_icon_size" + android:layout_height="@dimen/notification_2025_left_icon_size" + android:layout_alignParentStart="true" + android:layout_margin="@dimen/notification_2025_margin" + android:background="@drawable/notification_large_icon_outline" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + android:visibility="gone" /> - <include layout="@layout/notification_template_text" - android:layout_height="wrap_content" - android:minHeight="@dimen/notification_text_height" + <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts --> + <include layout="@layout/notification_2025_conversation_icon_container" /> + + <FrameLayout + android:id="@+id/alternate_expand_target" + android:layout_width="@dimen/notification_2025_content_margin_start" + android:layout_height="match_parent" + android:layout_gravity="start" + android:importantForAccessibility="no" + android:focusable="false" /> - </LinearLayout> + <LinearLayout + android:id="@+id/notification_headerless_view_row" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:orientation="horizontal" + android:clipChildren="false" + > - <FrameLayout - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" - > + <LinearLayout + android:id="@+id/notification_headerless_view_column" + android:layout_width="0px" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:layout_marginBottom="@dimen/notification_2025_margin" + android:layout_marginTop="@dimen/notification_2025_margin" + android:clipChildren="false" + android:orientation="vertical" + > - <include - layout="@layout/notification_2025_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top|end" - /> + <NotificationTopLineView + android:id="@+id/notification_top_line" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_headerless_line_height" + android:clipChildren="false" + android:theme="@style/Theme.DeviceDefault.Notification" + > + + <!-- + NOTE: The notification_2025_top_line_views layout contains the app_name_text. + In order to include the title view at the beginning, the Notification.Builder + has logic to hide that view whenever this title view is to be visible. + --> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:ellipsize="end" + android:fadingEdge="horizontal" + android:singleLine="true" + android:textAlignment="viewStart" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" + /> + + <include layout="@layout/notification_2025_top_line_views" /> + + </NotificationTopLineView> - </FrameLayout> + <LinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:clipChildren="false" + > + <com.android.internal.widget.NotificationVanishingFrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_headerless_line_height" + > + <!-- This is the simplest way to keep this text vertically centered without + gravity="center_vertical" which causes jumpiness in expansion animations. --> + <include + layout="@layout/notification_2025_text" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_text_height" + android:layout_gravity="center_vertical" + android:layout_marginTop="0dp" + /> + </com.android.internal.widget.NotificationVanishingFrameLayout> + </LinearLayout> + </LinearLayout> + + <ImageView + android:id="@+id/right_icon" + android:layout_width="@dimen/notification_right_icon_size" + android:layout_height="@dimen/notification_right_icon_size" + android:layout_gravity="center_vertical|end" + android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" + android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" + android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:background="@drawable/notification_large_icon_outline" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + /> + + <FrameLayout + android:id="@+id/expand_button_touch_container" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="@dimen/notification_content_margin_end" + > + + <include layout="@layout/notification_2025_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|end" + /> + + </FrameLayout> + + </LinearLayout> + + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + + </com.android.internal.widget.NotificationMaxHeightFrameLayout> + + <LinearLayout + android:id="@+id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="-20dp" + android:clipChildren="false" + android:orientation="vertical"> + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_content_margin" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" /> + <include layout="@layout/notification_material_action_list" /> + </LinearLayout> </LinearLayout> </com.android.internal.widget.CallLayout> diff --git a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml new file mode 100644 index 000000000000..f80411103501 --- /dev/null +++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml @@ -0,0 +1,216 @@ +<?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 + --> + +<!-- extends FrameLayout --> +<com.android.internal.widget.ConversationLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:tag="conversation" + > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:orientation="vertical" + > + + + <com.android.internal.widget.NotificationMaxHeightFrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_2025_min_height" + android:clipChildren="false" + > + + <ImageView + android:id="@+id/left_icon" + android:layout_width="@dimen/notification_2025_left_icon_size" + android:layout_height="@dimen/notification_2025_left_icon_size" + android:layout_alignParentStart="true" + android:layout_margin="@dimen/notification_2025_margin" + android:background="@drawable/notification_large_icon_outline" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + android:visibility="gone" + /> + + <include layout="@layout/notification_2025_conversation_icon_container" /> + + <FrameLayout + android:id="@+id/alternate_expand_target" + android:layout_width="@dimen/notification_2025_content_margin_start" + android:layout_height="match_parent" + android:layout_gravity="start" + android:importantForAccessibility="no" + android:focusable="false" + /> + + <!-- + NOTE: to make the expansion animation of id/notification_messaging happen vertically, + its X positioning must be the left edge of the notification, so instead of putting the + layout_marginStart on the id/notification_headerless_view_row, we put it on + id/notification_top_line, making the layout here just a bit different from the base. + --> + <LinearLayout + android:id="@+id/notification_headerless_view_row" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipChildren="false" + > + + <!-- + NOTE: because messaging will always have 2 lines, this LinearLayout should NOT + have the id/notification_headerless_view_column, as that is used for modifying + vertical margins to accommodate the single-line state that base supports + --> + <LinearLayout + android:layout_width="0px" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:layout_marginBottom="@dimen/notification_2025_margin" + android:layout_marginTop="@dimen/notification_2025_margin" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:clipChildren="false" + android:orientation="vertical" + > + + <NotificationTopLineView + android:id="@+id/notification_top_line" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_headerless_line_height" + android:clipChildren="false" + android:theme="@style/Theme.DeviceDefault.Notification" + > + + <!-- + NOTE: The notification_2025_top_line_views layout contains the app_name_text. + In order to include the title view at the beginning, the Notification.Builder + has logic to hide that view whenever this title view is to be visible. + --> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:ellipsize="end" + android:fadingEdge="horizontal" + android:singleLine="true" + android:textAlignment="viewStart" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" + /> + + <include layout="@layout/notification_2025_top_line_views" /> + + </NotificationTopLineView> + + <LinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:clipChildren="false" + > + <com.android.internal.widget.MessagingLinearLayout + android:id="@+id/notification_messaging" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:spacing="@dimen/notification_messaging_spacing" /> + </LinearLayout> + + </LinearLayout> + + <!-- Images --> + <com.android.internal.widget.MessagingLinearLayout + android:id="@+id/conversation_image_message_container" + android:layout_width="@dimen/notification_right_icon_size" + android:layout_height="@dimen/notification_right_icon_size" + android:layout_gravity="center_vertical|end" + android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" + android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" + android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:forceHasOverlappingRendering="false" + android:spacing="0dp" + android:clipChildren="false" + android:visibility="gone" + /> + + <ImageView + android:id="@+id/right_icon" + android:layout_width="@dimen/notification_right_icon_size" + android:layout_height="@dimen/notification_right_icon_size" + android:layout_gravity="center_vertical|end" + android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" + android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" + android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:background="@drawable/notification_large_icon_outline" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + /> + + <FrameLayout + android:id="@+id/expand_button_touch_container" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="@dimen/notification_content_margin_end" + > + + <include layout="@layout/notification_2025_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|end" + /> + + </FrameLayout> + + </LinearLayout> + + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + + </com.android.internal.widget.NotificationMaxHeightFrameLayout> + + <LinearLayout + android:id="@+id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="-20dp" + android:clipChildren="false" + android:orientation="vertical"> + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_content_margin" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" /> + <include layout="@layout/notification_material_action_list" /> + </LinearLayout> +</LinearLayout> +</com.android.internal.widget.ConversationLayout> diff --git a/core/res/res/layout/notification_2025_template_conversation.xml b/core/res/res/layout/notification_2025_template_conversation.xml deleted file mode 100644 index 24b6ad09d3f7..000000000000 --- a/core/res/res/layout/notification_2025_template_conversation.xml +++ /dev/null @@ -1,159 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<!-- extends FrameLayout --> -<com.android.internal.widget.ConversationLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/status_bar_latest_event_content" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:clipChildren="false" - android:tag="conversation" - android:theme="@style/Theme.DeviceDefault.Notification" - > - - <include layout="@layout/notification_2025_conversation_icon_container" /> - - <!-- Wraps entire "expandable" notification --> - <com.android.internal.widget.RemeasuringLinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="top" - android:clipToPadding="false" - android:clipChildren="false" - android:orientation="vertical" - > - <!-- LinearLayout for Expand Button--> - <com.android.internal.widget.RemeasuringLinearLayout - android:id="@+id/expand_button_and_content_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" - android:gravity="start|top" - android:orientation="horizontal" - android:clipChildren="false" - android:clipToPadding="false"> - <!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc--> - <com.android.internal.widget.RemeasuringLinearLayout - android:id="@+id/notification_action_list_margin_target" - android:layout_width="0dp" - android:orientation="vertical" - android:layout_height="wrap_content" - android:layout_weight="1"> - - <!-- Header --> - - <!-- Use layout_marginStart instead of paddingStart to work around strange - measurement behavior on lower display densities. --> - <include - layout="@layout/notification_2025_conversation_header" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="2dp" - android:layout_marginStart="@dimen/notification_2025_content_margin_start" - /> - - <!-- Messages --> - <com.android.internal.widget.MessagingLinearLayout - android:id="@+id/notification_messaging" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="@dimen/notification_text_size" - android:spacing="@dimen/notification_messaging_spacing" - android:clipToPadding="false" - android:clipChildren="false" - /> - </com.android.internal.widget.RemeasuringLinearLayout> - - <!-- This is where the expand button container will be placed when collapsed--> - </com.android.internal.widget.RemeasuringLinearLayout> - - <include layout="@layout/notification_template_smart_reply_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/notification_content_margin" - android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" /> - <include layout="@layout/notification_material_action_list" /> - </com.android.internal.widget.RemeasuringLinearLayout> - - <!--expand_button_a11y_container ensures talkback focus order is correct when view is expanded. - The -1px of marginTop and 1px of paddingTop make sure expand_button_a11y_container is prior to - its sibling view in accessibility focus order. - {see android.view.ViewGroup.addChildrenForAccessibility()} - expand_button_container will be moved under expand_button_and_content_container when collapsed, - this dynamic movement ensures message can flow under expand button when expanded--> - <FrameLayout - android:id="@+id/expand_button_a11y_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="end|top" - android:clipChildren="false" - android:clipToPadding="false" - android:layout_marginTop="-1px" - android:paddingTop="1px" - > - <!--expand_button_container is dynamically placed between here and at the end of the - layout. It starts here since only FrameLayout layout params have gravity--> - <LinearLayout - android:id="@+id/expand_button_container" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_gravity="end|top" - android:clipChildren="false" - android:clipToPadding="false" - android:orientation="vertical"> - <include layout="@layout/notification_close_button" - android:layout_width="@dimen/notification_close_button_size" - android:layout_height="@dimen/notification_close_button_size" - android:layout_gravity="end" - android:layout_marginEnd="20dp" - /> - <!--expand_button_touch_container makes sure that we can nicely center the expand - content in the collapsed layout while the parent makes sure that we're never laid out - bigger than the messaging content.--> - <LinearLayout - android:id="@+id/expand_button_touch_container" - android:layout_width="wrap_content" - android:layout_height="@dimen/conversation_expand_button_height" - android:orientation="horizontal" - android:layout_gravity="end|top" - android:paddingEnd="0dp" - android:clipToPadding="false" - android:clipChildren="false" - > - <!-- Images --> - <com.android.internal.widget.MessagingLinearLayout - android:id="@+id/conversation_image_message_container" - android:forceHasOverlappingRendering="false" - android:layout_width="40dp" - android:layout_height="40dp" - android:layout_marginStart="@dimen/conversation_image_start_margin" - android:spacing="0dp" - android:layout_gravity="center" - android:clipToPadding="false" - android:clipChildren="false" - /> - <include layout="@layout/notification_2025_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top|end" - /> - </LinearLayout> - </LinearLayout> - </FrameLayout> -</com.android.internal.widget.ConversationLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml index 0be61253c917..bbc29664d594 100644 --- a/core/res/res/layout/notification_2025_template_expanded_call.xml +++ b/core/res/res/layout/notification_2025_template_expanded_call.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2024 The Android Open Source Project + ~ 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. @@ -27,83 +27,47 @@ > <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts --> - <include layout="@layout/notification_2025_conversation_icon_container" /> + <include layout="@layout/notification_2025_conversation_header"/> - <LinearLayout + <com.android.internal.widget.RemeasuringLinearLayout android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/notification_content_margin" - android:orientation="vertical" - > - - <LinearLayout + android:layout_gravity="top" + android:clipChildren="false" + android:orientation="vertical"> + + <!-- Note: the top margin is being set in code based on the estimated space needed for + the header text. --> + <com.android.internal.widget.RemeasuringLinearLayout + android:id="@+id/notification_main_column" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_gravity="top" android:layout_weight="1" - android:orientation="horizontal" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:orientation="vertical" + android:clipChildren="false" > - <LinearLayout - android:id="@+id/notification_main_column" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_weight="1" - android:orientation="vertical" - android:minHeight="68dp" - > - - <include - layout="@layout/notification_2025_conversation_header" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - /> - - <include layout="@layout/notification_template_text_multiline" /> - - <include - android:layout_width="match_parent" - android:layout_height="@dimen/notification_progress_bar_height" - android:layout_marginTop="@dimen/notification_progress_margin_top" - layout="@layout/notification_template_progress" - /> - </LinearLayout> - - <FrameLayout - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" - > - - <include - layout="@layout/notification_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - /> + <include layout="@layout/notification_template_part_line1"/> - </FrameLayout> + <include layout="@layout/notification_template_text_multiline" /> - </LinearLayout> + </com.android.internal.widget.RemeasuringLinearLayout> - <ViewStub - android:layout="@layout/notification_material_reply_text" - android:id="@+id/notification_material_reply_container" + <include layout="@layout/notification_template_smart_reply_container" android:layout_width="match_parent" android:layout_height="wrap_content" - /> - - <include - layout="@layout/notification_template_smart_reply_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" android:layout_marginTop="@dimen/notification_content_margin" - /> + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" /> <include layout="@layout/notification_material_action_list" /> - </LinearLayout> + </com.android.internal.widget.RemeasuringLinearLayout> + + <include layout="@layout/notification_template_right_icon" /> </com.android.internal.widget.CallLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_conversation.xml b/core/res/res/layout/notification_2025_template_expanded_conversation.xml new file mode 100644 index 000000000000..d7e8bb3b6da2 --- /dev/null +++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml @@ -0,0 +1,75 @@ +<?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 + --> + +<!-- extends FrameLayout --> +<com.android.internal.widget.ConversationLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipToPadding="false" + android:clipChildren="false" + android:tag="conversation" + > + + <include layout="@layout/notification_2025_conversation_header"/> + + <com.android.internal.widget.RemeasuringLinearLayout + android:id="@+id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:clipChildren="false" + android:orientation="vertical"> + + <!-- Note: the top margin is being set in code based on the estimated space needed for + the header text. --> + <com.android.internal.widget.RemeasuringLinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_weight="1" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:orientation="vertical" + android:clipChildren="false" + > + + <include layout="@layout/notification_template_part_line1"/> + + <com.android.internal.widget.MessagingLinearLayout + android:id="@+id/notification_messaging" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:spacing="@dimen/notification_messaging_spacing" /> + </com.android.internal.widget.RemeasuringLinearLayout> + + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_content_margin" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" /> + + <include layout="@layout/notification_material_action_list" /> + + </com.android.internal.widget.RemeasuringLinearLayout> + + <include layout="@layout/notification_template_right_icon" /> + +</com.android.internal.widget.ConversationLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml index 177706c6d58d..20abfee6a4b6 100644 --- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml +++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml @@ -36,17 +36,21 @@ android:clipChildren="false" android:orientation="vertical"> + <!-- Note: the top margin is being set in code based on the estimated space needed for + the header text. --> <com.android.internal.widget.RemeasuringLinearLayout android:id="@+id/notification_main_column" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_weight="1" - android:layout_marginTop="@dimen/notification_2025_header_height" android:layout_marginEnd="@dimen/notification_content_margin_end" android:orientation="vertical" android:clipChildren="false" > + + <include layout="@layout/notification_template_part_line1"/> + <com.android.internal.widget.MessagingLinearLayout android:id="@+id/notification_messaging" android:layout_width="match_parent" diff --git a/core/res/res/layout/notification_2025_top_line_views.xml b/core/res/res/layout/notification_2025_top_line_views.xml index 74873463391e..7431c421ed3d 100644 --- a/core/res/res/layout/notification_2025_top_line_views.xml +++ b/core/res/res/layout/notification_2025_top_line_views.xml @@ -20,7 +20,7 @@ <merge xmlns:android="http://schemas.android.com/apk/res/android"> - <TextView + <com.android.internal.widget.ObservableTextView android:id="@+id/app_name_text" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -108,6 +108,42 @@ android:visibility="gone" /> + <TextView + android:id="@+id/verification_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:text="@string/notification_header_divider_symbol" + android:singleLine="true" + android:visibility="gone" + /> + + <ImageView + android:id="@+id/verification_icon" + android:layout_width="@dimen/notification_2025_badge_size" + android:layout_height="@dimen/notification_2025_badge_size" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:baseline="@dimen/notification_2025_badge_baseline" + android:scaleType="fitCenter" + android:visibility="gone" + /> + + <TextView + android:id="@+id/verification_text" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:layout_weight="100" + android:showRelative="true" + android:singleLine="true" + android:visibility="gone" + /> + <ImageButton android:id="@+id/feedback" android:layout_width="@dimen/notification_feedback_size" diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index f2ec56c69374..59ed25a1865f 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -64,6 +64,13 @@ <integer name="auto_data_switch_performance_stability_time_threshold_millis">120000</integer> <java-symbol type="integer" name="auto_data_switch_performance_stability_time_threshold_millis" /> + <!-- Define the bar for switching data back to the default SIM when both SIMs are out of service + in milliseconds. A value of 0 means an immediate switch, otherwise for a negative value, + the threshold defined by auto_data_switch_availability_stability_time_threshold_millis + will be used instead. --> + <integer name="auto_data_switch_availability_switchback_stability_time_threshold_millis">150000</integer> + <java-symbol type="integer" name="auto_data_switch_availability_switchback_stability_time_threshold_millis" /> + <!-- Define the maximum retry times when a validation for switching failed.--> <integer name="auto_data_switch_validation_max_retry">7</integer> <java-symbol type="integer" name="auto_data_switch_validation_max_retry" /> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 26f0ab3f28e1..b013ffd41ecb 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3264,6 +3264,7 @@ <java-symbol type="dimen" name="notification_content_margin" /> <java-symbol type="dimen" name="notification_2025_margin" /> <java-symbol type="dimen" name="notification_2025_content_margin_top" /> + <java-symbol type="dimen" name="notification_2025_content_margin_start" /> <java-symbol type="dimen" name="notification_2025_expand_button_horizontal_icon_padding" /> <java-symbol type="dimen" name="notification_2025_expand_button_reduced_end_padding" /> <java-symbol type="dimen" name="notification_progress_margin_horizontal" /> @@ -4705,7 +4706,8 @@ <java-symbol type="dimen" name="conversation_icon_container_top_padding" /> <java-symbol type="dimen" name="conversation_icon_container_top_padding_small_avatar" /> <java-symbol type="layout" name="notification_template_material_conversation" /> - <java-symbol type="layout" name="notification_2025_template_conversation" /> + <java-symbol type="layout" name="notification_2025_template_collapsed_conversation" /> + <java-symbol type="layout" name="notification_2025_template_expanded_conversation" /> <java-symbol type="dimen" name="button_padding_horizontal_material" /> <java-symbol type="dimen" name="button_inset_horizontal_material" /> <java-symbol type="layout" name="conversation_face_pile_layout" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 4c49ff849d49..05fb5735972e 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -249,8 +249,14 @@ android_library { android_ravenwood_test { name: "FrameworksCoreTestsRavenwood", libs: [ - "android.test.base.stubs.system", - "android.test.runner.stubs.system", + "android.test.base.stubs", + "android.test.mock.stubs", + "android.test.runner.stubs", + "android.view.flags-aconfig-java", + "ext", + "framework", + "framework-res", + "org.apache.http.legacy.stubs", ], static_libs: [ "androidx.annotation_annotation", @@ -264,6 +270,7 @@ android_ravenwood_test { "flag-junit", "flag-junit", "perfetto_trace_java_protos", + "platform-compat-test-rules", "platform-test-annotations", "testng", ], @@ -278,8 +285,12 @@ android_ravenwood_test { "src/android/content/res/*.java", "src/android/content/res/*.kt", "src/android/database/CursorWindowTest.java", + "src/android/graphics/*.java", + "src/android/graphics/*.kt", "src/android/os/**/*.java", "src/android/telephony/PinResultTest.java", + "src/android/text/**/*.java", + "src/android/text/**/*.kt", "src/android/util/**/*.java", "src/android/view/DisplayAdjustmentsTests.java", "src/android/view/DisplayInfoTest.java", @@ -288,20 +299,21 @@ android_ravenwood_test { "src/com/android/internal/os/**/*.java", "src/com/android/internal/power/EnergyConsumerStatsTest.java", "src/com/android/internal/ravenwood/**/*.java", - - // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests, - // to avoid having a dependency to FrameworksCoreTests. - // This way, when updating source files and running this test, we don't need to - // rebuild the entire FrameworksCoreTests, which would be slow. "src/com/android/internal/util/**/*.java", ":FrameworksCoreTestDoubles-sources", ":FrameworksCoreTests-aidl", ":FrameworksCoreTests-helpers", + + // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests, + // to avoid having a dependency to FrameworksCoreTests. + // This way, when updating source files and running this test, we don't need to + // rebuild the entire FrameworksCoreTests, which would be slow. ":FrameworksCoreTests-resonly{.aapt.srcjar}", ], exclude_srcs: [ "src/android/content/res/FontScaleConverterActivityTest.java", + "src/android/graphics/GraphicsPerformanceTests.java", ], resource_apk: "FrameworksCoreTests-resonly", aidl: { @@ -313,6 +325,7 @@ android_ravenwood_test { "res/xml/power_profile_test_cpu_legacy.xml", "res/xml/power_profile_test_modem.xml", ], + sdk_version: "core_platform", auto_gen_config: true, team: "trendy_team_ravenwood", } diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt index 0e5d92688123..2c614424a9a5 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt @@ -17,10 +17,8 @@ package android.content.res import android.platform.test.annotations.Presubmit -import android.platform.test.ravenwood.RavenwoodRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertWithMessage -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -28,9 +26,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class FontScaleConverterTest { - @get:Rule - val ravenwoodRule: RavenwoodRule = RavenwoodRule.Builder().build() - @Test fun straightInterpolation() { val table = createTable(8f to 8f, 10f to 10f, 20f to 20f) diff --git a/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java b/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java index 84bdbe03df13..263307ee3df7 100644 --- a/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java +++ b/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java @@ -20,7 +20,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import android.os.MemoryFile; import android.os.ParcelFileDescriptor; +import android.platform.test.annotations.DisabledOnRavenwood; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -37,6 +39,7 @@ public class BitmapFactoryTest { // tests that we can decode bitmaps from MemoryFiles @SmallTest @Test + @DisabledOnRavenwood(blockedBy = MemoryFile.class) public void testBitmapParcelFileDescriptor() throws Exception { Bitmap bitmap1 = Bitmap.createBitmap( new int[] { Color.BLUE }, 1, 1, Bitmap.Config.RGB_565); diff --git a/core/tests/coretests/src/android/graphics/BitmapTest.java b/core/tests/coretests/src/android/graphics/BitmapTest.java index 0126d367eb20..61c3d7813e88 100644 --- a/core/tests/coretests/src/android/graphics/BitmapTest.java +++ b/core/tests/coretests/src/android/graphics/BitmapTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.hardware.HardwareBuffer; +import android.platform.test.annotations.DisabledOnRavenwood; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -252,6 +253,7 @@ public class BitmapTest { | GraphicBuffer.USAGE_SW_WRITE_OFTEN; @Test + @DisabledOnRavenwood(blockedBy = HardwareBuffer.class) public void testWrapHardwareBufferWithSrgbColorSpace() { GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888, GRAPHICS_USAGE); Canvas canvas = buffer.lockCanvas(); @@ -265,6 +267,7 @@ public class BitmapTest { } @Test + @DisabledOnRavenwood(blockedBy = HardwareBuffer.class) public void testWrapHardwareBufferWithDisplayP3ColorSpace() { GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888, GRAPHICS_USAGE); Canvas canvas = buffer.lockCanvas(); diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java index 56760d77e28b..deb5157bb339 100644 --- a/core/tests/coretests/src/android/graphics/PaintTest.java +++ b/core/tests/coretests/src/android/graphics/PaintTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -97,6 +98,7 @@ public class PaintTest { @SmallTest @Test + @DisabledOnRavenwood(bug = 391381043) public void testHintingWidth() { final Typeface fontTypeface = Typeface.createFromAsset( InstrumentationRegistry.getInstrumentation().getContext().getAssets(), FONT_PATH); @@ -143,6 +145,7 @@ public class PaintTest { } @Test + @DisabledOnRavenwood(bug = 391381043) public void testHasGlyph_variationSelectors() { final Typeface fontTypeface = Typeface.createFromAsset( InstrumentationRegistry.getInstrumentation().getContext().getAssets(), diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 2b6eda8f0988..dc3376e09b15 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -35,9 +35,9 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.text.FontConfig; import android.util.ArrayMap; -import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.text.flags.Flags; @@ -63,9 +63,6 @@ import java.util.Map; @SmallTest @RunWith(AndroidJUnit4.class) public class TypefaceSystemFallbackTest { - private static final String SYSTEM_FONT_DIR = "/system/fonts/"; - private static final String SYSTEM_FONTS_XML = "/system/etc/fonts.xml"; - private static final String[] TEST_FONT_FILES = { "a3em.ttf", // Supports "a","b","c". The width of "a" is 3em, others are 1em. "b3em.ttf", // Supports "a","b","c". The width of "b" is 3em, others are 1em. @@ -118,8 +115,6 @@ public class TypefaceSystemFallbackTest { @Before public void setUp() { - final AssetManager am = - InstrumentationRegistry.getInstrumentation().getContext().getAssets(); for (final String fontFile : TEST_FONT_FILES) { final String sourceInAsset = "fonts/" + fontFile; copyAssetToFile(sourceInAsset, new File(TEST_FONT_DIR, fontFile)); @@ -216,7 +211,8 @@ public class TypefaceSystemFallbackTest { FontConfig fontConfig; try { fontConfig = FontListParser.parse( - SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null, 0, 0); + SystemFonts.LEGACY_FONTS_XML, SystemFonts.SYSTEM_FONT_DIR, + null, TEST_OEM_DIR, null, 0, 0); } catch (IOException | XmlPullParserException e) { throw new RuntimeException(e); } diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java index 80efa511d163..0c8b5ab5f3f9 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.os.SharedMemory; +import android.platform.test.annotations.DisabledOnRavenwood; import android.text.FontConfig; import android.util.ArrayMap; @@ -196,6 +197,7 @@ public class TypefaceTest { @SmallTest @Test + @DisabledOnRavenwood(blockedBy = SharedMemory.class) public void testSerialize() throws Exception { FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig(); Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig); diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index 9383807ec761..8ef105f79988 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -56,6 +56,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -116,7 +117,8 @@ public class DisplayManagerGlobalTest { @Test public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException { mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, - ALL_DISPLAY_EVENTS, /* packageName= */ null); + ALL_DISPLAY_EVENTS, /* packageName= */ null, + /* isEventFilterExplicit */ true); Mockito.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong()); IDisplayManagerCallback callback = mCallbackCaptor.getValue(); @@ -151,7 +153,7 @@ public class DisplayManagerGlobalTest { mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | INTERNAL_EVENT_FLAG_DISPLAY_STATE, - null); + null, /* isEventFilterExplicit */ true); Mockito.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong()); IDisplayManagerCallback callback = mCallbackCaptor.getValue(); @@ -172,11 +174,80 @@ public class DisplayManagerGlobalTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED) + public void test_refreshRateRegistration_implicitRRCallbacksEnabled() + throws RemoteException { + // Subscription without supplied events doesn't subscribe to RR events + mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, + ALL_DISPLAY_EVENTS, /* packageName= */ null, + /* isEventFilterExplicit */ false); + Mockito.verify(mDisplayManager) + .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS)); + + // After registering to refresh rate changes, subscription without supplied events subscribe + // to RR events + mDisplayManagerGlobal.registerForRefreshRateChanges(); + mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, + ALL_DISPLAY_EVENTS, /* packageName= */ null, + /* isEventFilterExplicit */ false); + Mockito.verify(mDisplayManager) + .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS + | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE)); + + // Assert all the existing listeners are also subscribed to RR events + CopyOnWriteArrayList<DisplayManagerGlobal.DisplayListenerDelegate> delegates = + mDisplayManagerGlobal.getDisplayListeners(); + for (DisplayManagerGlobal.DisplayListenerDelegate delegate: delegates) { + assertEquals(ALL_DISPLAY_EVENTS | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE, + delegate.mInternalEventFlagsMask); + } + + // Subscription to RR when events are supplied doesn't happen + mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, + ALL_DISPLAY_EVENTS, /* packageName= */ null, + /* isEventFilterExplicit */ true); + Mockito.verify(mDisplayManager) + .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS)); + + // Assert one listeners are not subscribed to RR events + delegates = mDisplayManagerGlobal.getDisplayListeners(); + int subscribedListenersCount = 0; + int nonSubscribedListenersCount = 0; + for (DisplayManagerGlobal.DisplayListenerDelegate delegate: delegates) { + + if (delegate.isEventFilterExplicit()) { + assertEquals(ALL_DISPLAY_EVENTS, delegate.mInternalEventFlagsMask); + nonSubscribedListenersCount++; + } else { + assertEquals(ALL_DISPLAY_EVENTS | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE, + delegate.mInternalEventFlagsMask); + subscribedListenersCount++; + } + } + + assertEquals(2, subscribedListenersCount); + assertEquals(1, nonSubscribedListenersCount); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED) + public void test_refreshRateRegistration_implicitRRCallbacksDisabled() + throws RemoteException { + mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, + ALL_DISPLAY_EVENTS, /* packageName= */ null, + /* isEventFilterExplicit */ false); + Mockito.verify(mDisplayManager) + .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS + | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE)); + } + + @Test public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException { // First we subscribe to all events in order to test that the subsequent calls to // registerDisplayListener will update the event mask. mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, - ALL_DISPLAY_EVENTS, /* packageName= */ null); + ALL_DISPLAY_EVENTS, /* packageName= */ null, + /* isEventFilterExplicit */ true); Mockito.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong()); IDisplayManagerCallback callback = mCallbackCaptor.getValue(); @@ -184,21 +255,24 @@ public class DisplayManagerGlobalTest { int displayId = 1; mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, ALL_DISPLAY_EVENTS - & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null); + & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null, + /* isEventFilterExplicit */ true); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); waitForHandler(); Mockito.verifyZeroInteractions(mDisplayListener); mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, ALL_DISPLAY_EVENTS - & ~DISPLAY_CHANGE_EVENTS, null); + & ~DISPLAY_CHANGE_EVENTS, null, + /* isEventFilterExplicit */ true); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); waitForHandler(); Mockito.verifyZeroInteractions(mDisplayListener); mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, ALL_DISPLAY_EVENTS - & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null); + & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null, + /* isEventFilterExplicit */ true); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); waitForHandler(); Mockito.verifyZeroInteractions(mDisplayListener); @@ -218,11 +292,34 @@ public class DisplayManagerGlobalTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED) + public void test_registerNativeRefreshRateCallbacks_enablesRRImplicitRegistrations() + throws RemoteException { + // Registering the display listener without supplied events doesn't subscribe to RR events + mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, + ALL_DISPLAY_EVENTS, /* packageName= */ null, + /* isEventFilterExplicit */ false); + Mockito.verify(mDisplayManager) + .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS)); + + // Native subscription for refresh rates is done + mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks(); + + // Registering the display listener without supplied events subscribe to RR events + mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, + ALL_DISPLAY_EVENTS, /* packageName= */ null, + /* isEventFilterExplicit */ false); + Mockito.verify(mDisplayManager) + .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS + | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE)); + } + + @Test public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners() throws RemoteException { mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED, - null); + null, /* isEventFilterExplicit */ true); InOrder inOrder = Mockito.inOrder(mDisplayManager); inOrder.verify(mDisplayManager) @@ -260,9 +357,10 @@ public class DisplayManagerGlobalTest { mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, - null /* packageName */); + null /* packageName */, /* isEventFilterExplicit */ true); mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler, - DISPLAY_CHANGE_EVENTS, null /* packageName */); + DISPLAY_CHANGE_EVENTS, null /* packageName */, + /* isEventFilterExplicit */ true); mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321); waitForHandler(); diff --git a/core/tests/coretests/src/android/os/BundleMergerTest.java b/core/tests/coretests/src/android/os/BundleMergerTest.java index 43ed821e647d..53799935e118 100644 --- a/core/tests/coretests/src/android/os/BundleMergerTest.java +++ b/core/tests/coretests/src/android/os/BundleMergerTest.java @@ -18,6 +18,7 @@ package android.os; import static android.os.BundleMerger.STRATEGY_ARRAY_APPEND; import static android.os.BundleMerger.STRATEGY_ARRAY_LIST_APPEND; +import static android.os.BundleMerger.STRATEGY_ARRAY_UNION; import static android.os.BundleMerger.STRATEGY_BOOLEAN_AND; import static android.os.BundleMerger.STRATEGY_BOOLEAN_OR; import static android.os.BundleMerger.STRATEGY_COMPARABLE_MAX; @@ -28,6 +29,7 @@ import static android.os.BundleMerger.STRATEGY_NUMBER_ADD; import static android.os.BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST; import static android.os.BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD; import static android.os.BundleMerger.STRATEGY_REJECT; +import static android.os.BundleMerger.STRATEGY_STRING_APPEND; import static android.os.BundleMerger.merge; import static org.junit.Assert.assertArrayEquals; @@ -204,6 +206,33 @@ public class BundleMergerTest { } @Test + public void testStrategyArrayUnion() throws Exception { + assertArrayEquals(new int[] {}, + (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {}, new int[] {})); + assertArrayEquals(new int[] {10}, + (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10}, new int[] {})); + assertArrayEquals(new int[] {20}, + (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {}, new int[] {20})); + assertArrayEquals(new int[] {10, 20}, + (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10}, new int[] {20})); + assertArrayEquals(new int[] {10, 20, 30, 40}, + (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10, 30}, new int[] {20, 40})); + assertArrayEquals(new int[] {10, 20, 30}, + (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10, 30}, new int[] {10, 20})); + assertArrayEquals(new int[] {10, 20}, + (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10, 20}, new int[] {20})); + assertArrayEquals(new String[] {"a", "b"}, + (String[]) merge(STRATEGY_ARRAY_UNION, new String[] {"a"}, new String[] {"b"})); + assertArrayEquals(new String[] {"a", "b", "c"}, + (String[]) merge(STRATEGY_ARRAY_UNION, new String[] {"a", "b"}, + new String[] {"b", "c"})); + + assertThrows(Exception.class, () -> { + merge(STRATEGY_ARRAY_UNION, 10, 20); + }); + } + + @Test public void testStrategyArrayListAppend() throws Exception { assertEquals(arrayListOf(), merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(), arrayListOf())); @@ -224,6 +253,18 @@ public class BundleMergerTest { } @Test + public void testStrategyStringAppend() throws Exception { + assertEquals("ab", merge(STRATEGY_STRING_APPEND, "a", "b")); + assertEquals("abc", merge(STRATEGY_STRING_APPEND, "a", "bc")); + assertEquals("abc", merge(STRATEGY_STRING_APPEND, "ab", "c")); + assertEquals("a,b,c,", merge(STRATEGY_STRING_APPEND, "a,", "b,c,")); + + assertThrows(Exception.class, () -> { + merge(STRATEGY_STRING_APPEND, 10, 20); + }); + } + + @Test public void testSetDefaultMergeStrategy() throws Exception { final BundleMerger merger = new BundleMerger(); merger.setDefaultMergeStrategy(STRATEGY_FIRST); diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java index fb743d2b42ad..69150150d6f9 100644 --- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java +++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java @@ -108,8 +108,8 @@ public class PerfettoTraceTest { PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.instant(FOO_CATEGORY, "event") - .addFlow(2) - .addTerminatingFlow(3) + .setFlow(2) + .setTerminatingFlow(3) .addArg("long_val", 10000000000L) .addArg("bool_val", true) .addArg("double_val", 3.14) diff --git a/core/tests/coretests/src/android/text/AndroidCharacterTest.java b/core/tests/coretests/src/android/text/AndroidCharacterTest.java index 1c5986a838fc..819a5fb8fd40 100644 --- a/core/tests/coretests/src/android/text/AndroidCharacterTest.java +++ b/core/tests/coretests/src/android/text/AndroidCharacterTest.java @@ -18,6 +18,7 @@ package android.text; import static org.junit.Assert.assertArrayEquals; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -26,6 +27,7 @@ import org.junit.Test; @Presubmit @SmallTest +@DisabledOnRavenwood(reason = "No need to make j.l.Character match behavior of AndroidCharacter") public class AndroidCharacterTest { @Test diff --git a/core/tests/coretests/src/android/text/SpanColorsTest.java b/core/tests/coretests/src/android/text/SpanColorsTest.java index d2cb8c160d21..4cdbd0886310 100644 --- a/core/tests/coretests/src/android/text/SpanColorsTest.java +++ b/core/tests/coretests/src/android/text/SpanColorsTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.graphics.Color; import android.graphics.drawable.ShapeDrawable; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; @@ -35,6 +36,7 @@ import org.junit.runner.RunWith; @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) +@DisabledOnRavenwood(blockedBy = ShapeDrawable.class) public class SpanColorsTest { private final TextPaint mWorkPaint = new TextPaint(); private SpanColors mSpanColors; diff --git a/core/tests/coretests/src/android/text/SpannableTest.java b/core/tests/coretests/src/android/text/SpannableTest.java index a3e6a7812324..710d1e2a3314 100644 --- a/core/tests/coretests/src/android/text/SpannableTest.java +++ b/core/tests/coretests/src/android/text/SpannableTest.java @@ -16,10 +16,10 @@ package android.text; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; -import android.test.MoreAsserts; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -50,13 +50,13 @@ public abstract class SpannableTest { // but other spans are not, unless the query region is empty, in // in which case any abutting spans are returned. spans = spannable.getSpans(0, 1, Object.class); - MoreAsserts.assertEquals(new Object[]{emptySpan}, spans); + assertArrayEquals(new Object[]{emptySpan}, spans); spans = spannable.getSpans(0, 2, Object.class); - MoreAsserts.assertEquals(new Object[]{emptySpan, unemptySpan}, spans); + assertArrayEquals(new Object[]{emptySpan, unemptySpan}, spans); spans = spannable.getSpans(1, 2, Object.class); - MoreAsserts.assertEquals(new Object[]{emptySpan, unemptySpan}, spans); + assertArrayEquals(new Object[]{emptySpan, unemptySpan}, spans); spans = spannable.getSpans(2, 2, Object.class); - MoreAsserts.assertEquals(new Object[]{unemptySpan}, spans); + assertArrayEquals(new Object[]{unemptySpan}, spans); } @Test diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java index 3541900dcacf..55f38b2fc2bd 100644 --- a/core/tests/coretests/src/android/text/StaticLayoutTest.java +++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java @@ -25,6 +25,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; import android.os.LocaleList; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import android.text.Layout.Alignment; import android.text.method.EditorState; @@ -726,6 +727,7 @@ public class StaticLayoutTest { } @Test + @DisabledOnRavenwood(bug = 391342883) public void testLocaleSpanAffectsHyphenation() { TextPaint paint = new TextPaint(); paint.setTextLocale(Locale.US); diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java index f552265cc507..e38c8800169a 100644 --- a/core/tests/coretests/src/android/text/TextUtilsTest.java +++ b/core/tests/coretests/src/android/text/TextUtilsTest.java @@ -18,6 +18,7 @@ package android.text; import static android.text.TextUtils.formatSimple; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -28,7 +29,6 @@ import static org.junit.Assert.fail; import android.os.Parcel; import android.platform.test.annotations.Presubmit; -import android.test.MoreAsserts; import android.text.style.StyleSpan; import android.text.util.Rfc822Token; import android.text.util.Rfc822Tokenizer; @@ -237,7 +237,7 @@ public class TextUtilsTest { for (String s : splitter) { strings.add(s); } - MoreAsserts.assertEquals(expectedStrings, strings.toArray(new String[]{})); + assertArrayEquals(expectedStrings, strings.toArray(new String[]{})); } @Test diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java index 59af6dd20478..c16393c2643d 100644 --- a/core/tests/coretests/src/android/text/format/DateFormatTest.java +++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java @@ -74,8 +74,9 @@ public class DateFormatTest { DateFormatSymbols dfs = DateFormat.getIcuDateFormatSymbols(Locale.US); assertEquals("AM", dfs.getAmPmStrings()[0]); assertEquals("PM", dfs.getAmPmStrings()[1]); - assertEquals("a", dfs.getAmpmNarrowStrings()[0]); - assertEquals("p", dfs.getAmpmNarrowStrings()[1]); + // getAmpmNarrowStrings() is a @CorePlatformApi that we should stop using in framework + // assertEquals("a", dfs.getAmpmNarrowStrings()[0]); + // assertEquals("p", dfs.getAmpmNarrowStrings()[1]); } @Test diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java index a07d399218e3..e54273479b80 100644 --- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java +++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java @@ -40,6 +40,7 @@ import static org.junit.Assert.assertTrue; import android.icu.util.Calendar; import android.icu.util.TimeZone; import android.icu.util.ULocale; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -686,6 +687,7 @@ public class DateIntervalFormatTest { } @Test + @DisabledOnRavenwood(bug = 391381043) public void testIsLibcoreVFlagEnabled() { // This flag has been fully ramped. It should never be false. assertTrue(DateIntervalFormat.isLibcoreVFlagEnabled()); diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java index 47be893eb3e9..a853d4a0c051 100644 --- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java +++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import android.content.res.Configuration; import android.content.res.Resources; import android.os.LocaleList; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -116,6 +117,7 @@ public class DateUtilsTest { } @Test + @DisabledOnRavenwood(reason = "DateFormat.set24HourTimePref is not available on host JVM") public void testFormatSameDayTime() { // This test assumes a default DateFormat.is24Hour setting. DateFormat.set24HourTimePref(null); diff --git a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java index c8cb5f38b185..49f3373d0659 100644 --- a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java +++ b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java @@ -18,6 +18,7 @@ package android.text.format; import static org.junit.Assert.assertEquals; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -72,6 +73,7 @@ public class TimeMigrationUtilsTest { * Compares TimeMigrationUtils.formatSimpleDateTime() with the code it is replacing. */ @Test + @DisabledOnRavenwood(blockedBy = Time.class) public void formatMillisAsDateTime_matchesOldBehavior() { // A selection of interesting locales. Locale[] locales = new Locale[] { diff --git a/core/tests/coretests/src/android/text/format/TimeTest.java b/core/tests/coretests/src/android/text/format/TimeTest.java index 6138ea1926dd..29c58998a635 100644 --- a/core/tests/coretests/src/android/text/format/TimeTest.java +++ b/core/tests/coretests/src/android/text/format/TimeTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import android.util.Log; import android.util.TimeFormatException; @@ -34,6 +35,7 @@ import org.junit.runner.RunWith; @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) +@DisabledOnRavenwood(blockedBy = Time.class) public class TimeTest { @Test diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java index a7ff244507cb..646e8f92fbb3 100644 --- a/core/tests/coretests/src/android/text/method/BackspaceTest.java +++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java @@ -16,6 +16,7 @@ package android.text.method; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import android.text.InputType; import android.util.KeyUtils; @@ -41,6 +42,7 @@ import org.junit.runner.RunWith; @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) +@DisabledOnRavenwood(blockedBy = EditText.class) public class BackspaceTest { private EditText mTextView; diff --git a/core/tests/coretests/src/android/text/method/EditorState.java b/core/tests/coretests/src/android/text/method/EditorState.java index 4eff7a49ac7b..633fa112c016 100644 --- a/core/tests/coretests/src/android/text/method/EditorState.java +++ b/core/tests/coretests/src/android/text/method/EditorState.java @@ -16,7 +16,7 @@ package android.text.method; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -181,4 +181,3 @@ public class EditorState { Assert.assertEquals(expected.mSelectionEnd, mSelectionEnd); } } - diff --git a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java index 1e4024d92f97..8044fd7a3432 100644 --- a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java +++ b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java @@ -16,6 +16,7 @@ package android.text.method; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import android.text.InputType; import android.util.KeyUtils; @@ -40,6 +41,7 @@ import org.junit.runner.RunWith; @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) +@DisabledOnRavenwood(blockedBy = EditText.class) public class ForwardDeleteTest { private EditText mTextView; diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java index e2c19024a840..37ad204ad64c 100644 --- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java +++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java @@ -19,6 +19,7 @@ package android.text.method; import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; @@ -44,6 +45,7 @@ import org.junit.runner.RunWith; @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) +@DisabledOnRavenwood(blockedBy = View.class) public class InsertModeTransformationMethodTest { private static View sView; private static final String TEXT = "abc def"; diff --git a/core/tests/coretests/src/android/text/util/LinkifyTest.java b/core/tests/coretests/src/android/text/util/LinkifyTest.java index 52f3b2e0534f..98bdb0b53df6 100644 --- a/core/tests/coretests/src/android/text/util/LinkifyTest.java +++ b/core/tests/coretests/src/android/text/util/LinkifyTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.res.Configuration; import android.os.LocaleList; +import android.platform.test.annotations.DisabledOnRavenwood; import android.text.Spannable; import android.text.SpannableString; import android.text.method.LinkMovementMethod; @@ -46,6 +47,7 @@ import java.util.Locale; */ @SmallTest @RunWith(AndroidJUnit4.class) +@DisabledOnRavenwood(blockedBy = Linkify.class) public class LinkifyTest { private static final LocaleList LOCALE_LIST_US = new LocaleList(Locale.US); diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index e6361e10cfa7..6adceb96d977 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -230,7 +230,9 @@ public class InsetsSourceConsumerTest { new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash, false /* initialVisible */, new Point(), Insets.NONE), new int[1], hideTypes, new int[1], new int[1]); - assertTrue(mRemoveSurfaceCalled); + if (!android.view.inputmethod.Flags.refactorInsetsController()) { + assertTrue(mRemoveSurfaceCalled); + } assertEquals(0, hideTypes[0]); }); diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java index 3239598eccdc..0ba2d851feb9 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.annotation.SuppressLint; import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseArray; @@ -287,6 +288,7 @@ public class KernelSingleUidTimeReaderTest { 0, lastUidCpuTimes.size()); } + @SuppressLint("CheckResult") @Test public void testAddDeltaFromBpf() { LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 5); diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java index 7e5d0a4c2e42..959e121aae9e 100644 --- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import android.annotation.SuppressLint; import android.os.BadParcelableException; import android.os.Parcel; import android.platform.test.ravenwood.RavenwoodRule; @@ -176,6 +177,7 @@ public class LongArrayMultiStateCounterTest { assertCounts(newCounter, 0, new long[]{116, 232, 364, 528}); } + @SuppressLint("CheckResult") private void assertCounts(LongArrayMultiStateCounter counter, int state, long[] expected) { long[] counts = new long[expected.length]; counter.getCounts(counts, state); diff --git a/graphics/java/android/graphics/AvoidXfermode.java b/graphics/java/android/graphics/AvoidXfermode.java index 683c15702427..5296ee848872 100644 --- a/graphics/java/android/graphics/AvoidXfermode.java +++ b/graphics/java/android/graphics/AvoidXfermode.java @@ -23,6 +23,7 @@ package android.graphics; * @removed */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AvoidXfermode extends Xfermode { // these need to match the enum in AvoidXfermode.h on the native side diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 9b9be244cf90..9f605342e378 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -26,6 +26,7 @@ import java.util.function.Consumer; /** * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class BLASTBufferQueue { // Note: This field is accessed by native code. public long mNativeObject; // BLASTBufferQueue* diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index a2a0f4936888..0ca58cc07213 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -48,6 +48,7 @@ import java.util.Objects; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class BaseCanvas { /** * Should only be assigned in constructors (or setBitmap if software canvas), diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index 5b1fa7b15e6d..0511bd15dd13 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -44,6 +44,7 @@ import java.util.Objects; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BaseRecordingCanvas extends Canvas { public BaseRecordingCanvas(long nativeCanvas) { diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 0c4ea79dd5be..cd5a54c2fd3f 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -51,6 +51,7 @@ import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.WeakHashMap; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Bitmap implements Parcelable { private static final String TAG = "Bitmap"; diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 1c2014183bb7..a5535c8d8485 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -41,6 +41,7 @@ import java.io.InputStream; * Creates Bitmap objects from various sources, including files, streams, * and byte-arrays. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BitmapFactory { private static final int DECODE_BUFFER_SIZE = 16 * 1024; diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java index 29112af9516b..9b3f7158a3e5 100644 --- a/graphics/java/android/graphics/BitmapRegionDecoder.java +++ b/graphics/java/android/graphics/BitmapRegionDecoder.java @@ -37,6 +37,7 @@ import java.io.InputStream; * to get a decoded Bitmap of the specified region. * */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class BitmapRegionDecoder { private long mNativeBitmapRegionDecoder; private boolean mRecycled; diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index dcfff62459ab..ac3543a403cc 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -31,6 +31,7 @@ import java.lang.annotation.RetentionPolicy; * Shader used to draw a bitmap as a texture. The bitmap can be repeated or * mirrored by setting the tiling mode. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BitmapShader extends Shader { /** * Prevent garbage collection. diff --git a/graphics/java/android/graphics/BlendMode.java b/graphics/java/android/graphics/BlendMode.java index c6ae680d01bf..c07af4e23b6f 100644 --- a/graphics/java/android/graphics/BlendMode.java +++ b/graphics/java/android/graphics/BlendMode.java @@ -19,6 +19,7 @@ package android.graphics; import android.annotation.NonNull; import android.annotation.Nullable; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public enum BlendMode { /** diff --git a/graphics/java/android/graphics/BlendModeColorFilter.java b/graphics/java/android/graphics/BlendModeColorFilter.java index d4e23732bdc3..d5dd0d3c5330 100644 --- a/graphics/java/android/graphics/BlendModeColorFilter.java +++ b/graphics/java/android/graphics/BlendModeColorFilter.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; * A color filter that can be used to tint the source pixels using a single * color and a specific {@link BlendMode}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class BlendModeColorFilter extends ColorFilter { @ColorInt final int mColor; diff --git a/graphics/java/android/graphics/BlurMaskFilter.java b/graphics/java/android/graphics/BlurMaskFilter.java index f3064f872041..22ed524e8ec0 100644 --- a/graphics/java/android/graphics/BlurMaskFilter.java +++ b/graphics/java/android/graphics/BlurMaskFilter.java @@ -22,6 +22,7 @@ package android.graphics; * inside, or straddles, the original mask's border, is controlled by the * Blur enum. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BlurMaskFilter extends MaskFilter { public enum Blur { diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java index 46640d7222ca..27b695c8c77f 100644 --- a/graphics/java/android/graphics/Camera.java +++ b/graphics/java/android/graphics/Camera.java @@ -21,6 +21,7 @@ package android.graphics; * generate a matrix that can be applied, for instance, on a * {@link Canvas}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Camera { /** * Creates a new camera, with empty transformations. diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 28c2ca36fd2e..9137150b104c 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -54,6 +54,7 @@ import java.lang.annotation.RetentionPolicy; * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html"> * Canvas and Drawables</a> developer guide.</p></div> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Canvas extends BaseCanvas { private static int sCompatibilityVersion = 0; private static boolean sCompatibilityRestore = false; diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java index e949584b0659..755161d387cc 100644 --- a/graphics/java/android/graphics/CanvasProperty.java +++ b/graphics/java/android/graphics/CanvasProperty.java @@ -25,6 +25,7 @@ import com.android.internal.util.VirtualRefBasePtr; * TODO: Make public? * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class CanvasProperty<T> { private VirtualRefBasePtr mProperty; diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java index 7050325997b6..918f26dfe640 100644 --- a/graphics/java/android/graphics/ColorFilter.java +++ b/graphics/java/android/graphics/ColorFilter.java @@ -23,6 +23,7 @@ import libcore.util.NativeAllocationRegistry; * each pixel drawn with that paint. This is an abstract class that should * never be used directly. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ColorFilter { private static class NoImagePreloadHolder { diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java index bfdf3187c575..cb78a8384994 100644 --- a/graphics/java/android/graphics/ColorMatrixColorFilter.java +++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java @@ -27,6 +27,7 @@ import android.os.Build; * * @see ColorMatrix */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ColorMatrixColorFilter extends ColorFilter { @UnsupportedAppUsage private final ColorMatrix mMatrix = new ColorMatrix(); diff --git a/graphics/java/android/graphics/Compatibility.java b/graphics/java/android/graphics/Compatibility.java index 747fbf111b4b..f89a4f7810c1 100644 --- a/graphics/java/android/graphics/Compatibility.java +++ b/graphics/java/android/graphics/Compatibility.java @@ -21,6 +21,7 @@ package android.graphics; * specified by the app. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Compatibility { private Compatibility() {} diff --git a/graphics/java/android/graphics/ComposePathEffect.java b/graphics/java/android/graphics/ComposePathEffect.java index 7d59ecea948e..b380d2e78d4c 100644 --- a/graphics/java/android/graphics/ComposePathEffect.java +++ b/graphics/java/android/graphics/ComposePathEffect.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ComposePathEffect extends PathEffect { /** diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java index e7145686247e..57a11d232ac5 100644 --- a/graphics/java/android/graphics/ComposeShader.java +++ b/graphics/java/android/graphics/ComposeShader.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; /** A subclass of shader that returns the composition of two other shaders, combined by an {@link android.graphics.Xfermode} subclass. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ComposeShader extends Shader { Shader mShaderA; diff --git a/graphics/java/android/graphics/CornerPathEffect.java b/graphics/java/android/graphics/CornerPathEffect.java index 8f4d7d9b1c49..37f0d2979e5e 100644 --- a/graphics/java/android/graphics/CornerPathEffect.java +++ b/graphics/java/android/graphics/CornerPathEffect.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class CornerPathEffect extends PathEffect { /** diff --git a/graphics/java/android/graphics/DashPathEffect.java b/graphics/java/android/graphics/DashPathEffect.java index ef3ebe8089ca..ff3c376be29d 100644 --- a/graphics/java/android/graphics/DashPathEffect.java +++ b/graphics/java/android/graphics/DashPathEffect.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DashPathEffect extends PathEffect { /** diff --git a/graphics/java/android/graphics/DiscretePathEffect.java b/graphics/java/android/graphics/DiscretePathEffect.java index 3b3c9c9be6c2..77f984589896 100644 --- a/graphics/java/android/graphics/DiscretePathEffect.java +++ b/graphics/java/android/graphics/DiscretePathEffect.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DiscretePathEffect extends PathEffect { /** diff --git a/graphics/java/android/graphics/DrawFilter.java b/graphics/java/android/graphics/DrawFilter.java index c7fdcb22c71d..505a830d725b 100644 --- a/graphics/java/android/graphics/DrawFilter.java +++ b/graphics/java/android/graphics/DrawFilter.java @@ -22,6 +22,7 @@ package android.graphics; * can disable/enable antialiasing, or change the color for everything this is * drawn. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class DrawFilter { /** diff --git a/graphics/java/android/graphics/EmbossMaskFilter.java b/graphics/java/android/graphics/EmbossMaskFilter.java index 003678ae5a3c..f0a661f57291 100644 --- a/graphics/java/android/graphics/EmbossMaskFilter.java +++ b/graphics/java/android/graphics/EmbossMaskFilter.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class EmbossMaskFilter extends MaskFilter { /** * Create an emboss maskfilter diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 88f0e8ef8a94..09022ee35e64 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -41,6 +41,7 @@ import java.nio.channels.FileChannel; * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class FontFamily { private static String TAG = "FontFamily"; diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 13c4a94cb9b6..8d0f12866bcf 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -48,6 +48,7 @@ import java.util.regex.Pattern; * Parser for font config files. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class FontListParser { private static final String TAG = "FontListParser"; diff --git a/graphics/java/android/graphics/ForceDarkType.java b/graphics/java/android/graphics/ForceDarkType.java index 396b03703bb9..d21aef30a45c 100644 --- a/graphics/java/android/graphics/ForceDarkType.java +++ b/graphics/java/android/graphics/ForceDarkType.java @@ -29,6 +29,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ForceDarkType { /** * Force dark disabled: normal, default operation. diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java index 3b8f46630344..520213892d01 100644 --- a/graphics/java/android/graphics/FrameInfo.java +++ b/graphics/java/android/graphics/FrameInfo.java @@ -38,6 +38,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class FrameInfo { public long[] frameInfo = new long[FRAME_INFO_SIZE]; diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java index 63ca3b8313ce..7fc13db85659 100644 --- a/graphics/java/android/graphics/Gainmap.java +++ b/graphics/java/android/graphics/Gainmap.java @@ -86,6 +86,7 @@ import java.lang.annotation.RetentionPolicy; * for these functions cancels out and does not affect the result, so other bases may be used * if preferred. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Gainmap implements Parcelable { /** @hide */ diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java index 6705b25ab0ec..4982851c65de 100644 --- a/graphics/java/android/graphics/GraphicBuffer.java +++ b/graphics/java/android/graphics/GraphicBuffer.java @@ -28,6 +28,7 @@ import android.os.Parcelable; * @hide */ @SuppressWarnings("UnusedDeclaration") +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class GraphicBuffer implements Parcelable { // Note: keep usage flags in sync with GraphicBuffer.h and gralloc.h public static final int USAGE_SW_READ_NEVER = 0x0; diff --git a/graphics/java/android/graphics/GraphicsProtos.java b/graphics/java/android/graphics/GraphicsProtos.java index 6bc41d39ff98..fa7eaf946845 100644 --- a/graphics/java/android/graphics/GraphicsProtos.java +++ b/graphics/java/android/graphics/GraphicsProtos.java @@ -24,6 +24,7 @@ import android.util.proto.ProtoOutputStream; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class GraphicsProtos { /** GraphicsProtos can never be an instance */ private GraphicsProtos() {} diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java index 7a012bcde799..d0b9998e18c8 100644 --- a/graphics/java/android/graphics/GraphicsStatsService.java +++ b/graphics/java/android/graphics/GraphicsStatsService.java @@ -74,6 +74,7 @@ import java.util.TimeZone; * for the process to use. * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class GraphicsStatsService extends IGraphicsStats.Stub { public static final String GRAPHICS_STATS_SERVICE = "graphicsstats"; diff --git a/graphics/java/android/graphics/HardwareBufferRenderer.java b/graphics/java/android/graphics/HardwareBufferRenderer.java index e04f13c9b922..81798709b7c6 100644 --- a/graphics/java/android/graphics/HardwareBufferRenderer.java +++ b/graphics/java/android/graphics/HardwareBufferRenderer.java @@ -55,6 +55,7 @@ import java.util.function.Consumer; * HardwareBufferRenderer will never clear contents before each draw invocation so previous contents * in the {@link HardwareBuffer} target will be preserved across renders. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class HardwareBufferRenderer implements AutoCloseable { private static final ColorSpace DEFAULT_COLORSPACE = ColorSpace.get(Named.SRGB); diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index ef6b72871415..3444f84c20af 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -79,6 +79,7 @@ import sun.misc.Cleaner; * Failure to do so will cause the render thread to stall on that surface, blocking all * HardwareRenderer instances.</p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class HardwareRenderer { private static final String LOG_TAG = "HardwareRenderer"; diff --git a/graphics/java/android/graphics/HardwareRendererObserver.java b/graphics/java/android/graphics/HardwareRendererObserver.java index d5a6a2fe158a..99263780f407 100644 --- a/graphics/java/android/graphics/HardwareRendererObserver.java +++ b/graphics/java/android/graphics/HardwareRendererObserver.java @@ -28,6 +28,7 @@ import java.lang.ref.WeakReference; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class HardwareRendererObserver { private final long[] mFrameMetrics; private final Handler mHandler; diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 639517996724..419929a39007 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -44,6 +44,7 @@ import android.media.MediaFormat; import android.net.Uri; import android.os.Build; import android.os.Trace; +import android.ravenwood.annotation.RavenwoodIgnore; import android.system.ErrnoException; import android.system.Os; import android.util.DisplayMetrics; @@ -173,6 +174,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * }); * </pre> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class ImageDecoder implements AutoCloseable { /** * Source of encoded image data. @@ -1987,6 +1989,7 @@ public final class ImageDecoder implements AutoCloseable { * Check if HEVC decoder is supported by the device. */ @SuppressWarnings("AndroidFrameworkCompatChange") + @RavenwoodIgnore(blockedBy = MediaCodecList.class) private static boolean isHevcDecoderSupported() { synchronized (sIsHevcDecoderSupportedLock) { if (sIsHevcDecoderSupportedInitialized) { @@ -2010,6 +2013,7 @@ public final class ImageDecoder implements AutoCloseable { * Checks if the device supports decoding 10-bit AV1. */ @SuppressWarnings("AndroidFrameworkCompatChange") // This is not an app-visible API. + @RavenwoodIgnore(blockedBy = MediaCodecList.class) private static boolean isP010SupportedForAV1() { synchronized (sIsP010SupportedLock) { if (sIsP010SupportedFlagsInitialized) { @@ -2025,6 +2029,7 @@ public final class ImageDecoder implements AutoCloseable { * This method is called by JNI. */ @SuppressWarnings("unused") + @RavenwoodIgnore(blockedBy = MediaCodecList.class) private static boolean isP010SupportedForHEVC() { synchronized (sIsP010SupportedLock) { if (sIsP010SupportedFlagsInitialized) { diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index b4899f975f43..4c9f5ac6ba92 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -24,6 +24,7 @@ import com.android.internal.camera.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ImageFormat { /** @hide */ @Retention(RetentionPolicy.SOURCE) diff --git a/graphics/java/android/graphics/LayerRasterizer.java b/graphics/java/android/graphics/LayerRasterizer.java index 25155ab284fb..1a44248d01b0 100644 --- a/graphics/java/android/graphics/LayerRasterizer.java +++ b/graphics/java/android/graphics/LayerRasterizer.java @@ -20,6 +20,7 @@ package android.graphics; * @removed feature is not supported by hw-accerlerated or PDF backends */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LayerRasterizer extends Rasterizer { public LayerRasterizer() { } diff --git a/graphics/java/android/graphics/LeakyTypefaceStorage.java b/graphics/java/android/graphics/LeakyTypefaceStorage.java index 618e60d442d7..25a843696cc5 100644 --- a/graphics/java/android/graphics/LeakyTypefaceStorage.java +++ b/graphics/java/android/graphics/LeakyTypefaceStorage.java @@ -32,6 +32,7 @@ import java.util.ArrayList; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LeakyTypefaceStorage { private static final Object sLock = new Object(); diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java index fe73a1a70b9c..1afdc7706396 100644 --- a/graphics/java/android/graphics/LightingColorFilter.java +++ b/graphics/java/android/graphics/LightingColorFilter.java @@ -40,6 +40,7 @@ import android.os.Build; * </pre> * The result is pinned to the <code>[0..255]</code> range for each channel. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LightingColorFilter extends ColorFilter { @ColorInt private int mMul; diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java index 087937144b97..c6566c9b27fe 100644 --- a/graphics/java/android/graphics/LinearGradient.java +++ b/graphics/java/android/graphics/LinearGradient.java @@ -24,6 +24,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LinearGradient extends Shader { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private float mX0; diff --git a/graphics/java/android/graphics/MaskFilter.java b/graphics/java/android/graphics/MaskFilter.java index d4743155729e..b490650e4d0c 100644 --- a/graphics/java/android/graphics/MaskFilter.java +++ b/graphics/java/android/graphics/MaskFilter.java @@ -21,6 +21,7 @@ package android.graphics; * an alpha-channel mask before drawing it. A subclass of MaskFilter may be * installed into a Paint. Blur and emboss are implemented as subclasses of MaskFilter. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class MaskFilter { protected void finalize() throws Throwable { diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index 6be8332e784b..f4d841b161fa 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -37,6 +37,7 @@ import java.nio.ShortBuffer; * for the mesh. Once generated, a mesh object can be drawn through * {@link Canvas#drawMesh(Mesh, BlendMode, Paint)} */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Mesh { private long mNativeMeshWrapper; private boolean mIsIndexed; diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java index b1aae7f37c31..9c7e948dfc1b 100644 --- a/graphics/java/android/graphics/MeshSpecification.java +++ b/graphics/java/android/graphics/MeshSpecification.java @@ -72,6 +72,7 @@ import java.lang.annotation.RetentionPolicy; * These should be kept in mind when generating a mesh specification, as exceeding them will * lead to errors. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class MeshSpecification { long mNativeMeshSpec; diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java index 9c9535d16aab..cefe391f2d2c 100644 --- a/graphics/java/android/graphics/Movie.java +++ b/graphics/java/android/graphics/Movie.java @@ -27,6 +27,7 @@ import java.io.InputStream; * @deprecated Prefer {@link android.graphics.drawable.AnimatedImageDrawable}. */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Movie { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private long mNativeMovie; diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index 382269f74366..00df23fe76ba 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -32,6 +32,7 @@ import android.compat.annotation.UnsupportedAppUsage; * using a WYSIWYG graphics editor. * </p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class NinePatch { /** * Struct of inset information attached to a 9 patch bitmap. diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 3d4dccf095f5..a0ca0988e03c 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -66,6 +66,7 @@ import java.util.Objects; * The Paint class holds the style and color information about how to draw * geometries, text and bitmaps. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Paint { private static final String TAG = "Paint"; diff --git a/graphics/java/android/graphics/PaintFlagsDrawFilter.java b/graphics/java/android/graphics/PaintFlagsDrawFilter.java index 232661113b5a..f4c49b11ea96 100644 --- a/graphics/java/android/graphics/PaintFlagsDrawFilter.java +++ b/graphics/java/android/graphics/PaintFlagsDrawFilter.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PaintFlagsDrawFilter extends DrawFilter { /** * Subclass of DrawFilter that affects every paint by first clearing diff --git a/graphics/java/android/graphics/PathDashPathEffect.java b/graphics/java/android/graphics/PathDashPathEffect.java index 2b6a6edcc266..dc92e6caabcd 100644 --- a/graphics/java/android/graphics/PathDashPathEffect.java +++ b/graphics/java/android/graphics/PathDashPathEffect.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PathDashPathEffect extends PathEffect { public enum Style { diff --git a/graphics/java/android/graphics/PathEffect.java b/graphics/java/android/graphics/PathEffect.java index 3292501e6324..9bb71935cfd0 100644 --- a/graphics/java/android/graphics/PathEffect.java +++ b/graphics/java/android/graphics/PathEffect.java @@ -21,6 +21,7 @@ package android.graphics; * the geometry of a drawing primitive before it is transformed by the * canvas' matrix and drawn. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PathEffect { protected void finalize() throws Throwable { diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java index d7caabf9f91b..1ed70d02d140 100644 --- a/graphics/java/android/graphics/PathIterator.java +++ b/graphics/java/android/graphics/PathIterator.java @@ -34,6 +34,7 @@ import java.util.Iterator; * <code>PathIterator</code> can be used to query a given {@link Path} object, to discover its * operations and point values. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PathIterator implements Iterator<PathIterator.Segment> { private final float[] mPointsArray; @@ -47,9 +48,11 @@ public class PathIterator implements Iterator<PathIterator.Segment> { private static final boolean IS_DALVIK = "dalvik".equalsIgnoreCase( System.getProperty("java.vm.name")); - private static final NativeAllocationRegistry sRegistry = - NativeAllocationRegistry.createMalloced( - PathIterator.class.getClassLoader(), nGetFinalizer()); + private static class NoImagePreloadHolder { + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + PathIterator.class.getClassLoader(), nGetFinalizer()); + } /** * The <code>Verb</code> indicates the operation for a given segment of a path. These @@ -69,6 +72,11 @@ public class PathIterator implements Iterator<PathIterator.Segment> { public static final int VERB_CLOSE = 5; public static final int VERB_DONE = 6; + + static { + // Keep <cinit> exist in bytecode + } + /** * Returns a {@link PathIterator} object for this path, which can be used to query the * data (operations and points) in the path. Iterators can only be used on Path objects @@ -90,7 +98,7 @@ public class PathIterator implements Iterator<PathIterator.Segment> { mPointsArray = new float[POINT_ARRAY_SIZE]; mPointsAddress = 0; } - sRegistry.registerNativeAllocation(this, mNativeIterator); + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeIterator); } /** diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java index 2c6cfa5c2e3d..4d123db41ee7 100644 --- a/graphics/java/android/graphics/PathMeasure.java +++ b/graphics/java/android/graphics/PathMeasure.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PathMeasure { private Path mPath; diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index ee4165b8da05..54eb2bc659bc 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -33,6 +33,7 @@ import java.io.OutputStream; * <p class="note"><strong>Note:</strong> Prior to API level 23 a picture cannot * be replayed on a hardware accelerated canvas.</p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Picture { private PictureCanvas mRecordingCanvas; // TODO: Figure out if this was a false-positive diff --git a/graphics/java/android/graphics/PixelXorXfermode.java b/graphics/java/android/graphics/PixelXorXfermode.java index 27884e07ecfb..64278525bb1f 100644 --- a/graphics/java/android/graphics/PixelXorXfermode.java +++ b/graphics/java/android/graphics/PixelXorXfermode.java @@ -20,6 +20,7 @@ package android.graphics; * @removed */ @Deprecated +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PixelXorXfermode extends Xfermode { public PixelXorXfermode(int opColor) { diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java index eb940e2f9017..730a804e17c3 100644 --- a/graphics/java/android/graphics/PorterDuff.java +++ b/graphics/java/android/graphics/PorterDuff.java @@ -26,6 +26,7 @@ import android.compat.annotation.UnsupportedAppUsage; * * Consider using {@link BlendMode} instead as it provides a wider variety of tinting options */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PorterDuff { /** * {@usesMathJax} diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java index 0700f217ecf0..777ef6ce906b 100644 --- a/graphics/java/android/graphics/PorterDuffColorFilter.java +++ b/graphics/java/android/graphics/PorterDuffColorFilter.java @@ -25,6 +25,7 @@ import android.os.Build; * A color filter that can be used to tint the source pixels using a single * color and a specific {@link PorterDuff Porter-Duff composite mode}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PorterDuffColorFilter extends ColorFilter { @ColorInt private int mColor; diff --git a/graphics/java/android/graphics/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java index 83d0507a5074..e10d7370d6f9 100644 --- a/graphics/java/android/graphics/PorterDuffXfermode.java +++ b/graphics/java/android/graphics/PorterDuffXfermode.java @@ -23,6 +23,7 @@ package android.graphics; * information on the available alpha compositing and blending modes.</p> * */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PorterDuffXfermode extends Xfermode { /** * Create an xfermode that uses the specified porter-duff mode. diff --git a/graphics/java/android/graphics/PostProcessor.java b/graphics/java/android/graphics/PostProcessor.java index 6fed39b9975d..066214ac6cd6 100644 --- a/graphics/java/android/graphics/PostProcessor.java +++ b/graphics/java/android/graphics/PostProcessor.java @@ -37,6 +37,7 @@ import android.graphics.drawable.Drawable; * * <p>Supplied to ImageDecoder via {@link ImageDecoder#setPostProcessor setPostProcessor}.</p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public interface PostProcessor { /** * Do any processing after (for example) decoding. diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java index e582e66e1627..06e92eae9c82 100644 --- a/graphics/java/android/graphics/RadialGradient.java +++ b/graphics/java/android/graphics/RadialGradient.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class RadialGradient extends Shader { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private float mX; diff --git a/graphics/java/android/graphics/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java index 575095426563..5e67da50a40d 100644 --- a/graphics/java/android/graphics/Rasterizer.java +++ b/graphics/java/android/graphics/Rasterizer.java @@ -24,6 +24,7 @@ package android.graphics; /** * @removed feature is not supported by hw-accerlerated or PDF backends */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Rasterizer { protected void finalize() throws Throwable { } diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java index cc5b3b94e0fa..a56f461e511a 100644 --- a/graphics/java/android/graphics/RecordingCanvas.java +++ b/graphics/java/android/graphics/RecordingCanvas.java @@ -33,6 +33,7 @@ import dalvik.annotation.optimization.CriticalNative; * {@link RenderNode#endRecording()} is called. It must not be retained beyond that as it is * internally reused. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class RecordingCanvas extends BaseRecordingCanvas { // The recording canvas pool should be large enough to handle a deeply nested // view hierarchy because display lists are generated recursively. diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java index 29708738d2db..e2215d4bf300 100644 --- a/graphics/java/android/graphics/Region.java +++ b/graphics/java/android/graphics/Region.java @@ -23,6 +23,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Pools.SynchronizedPool; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Region implements Parcelable { private static final int MAX_POOL_SIZE = 10; diff --git a/graphics/java/android/graphics/RegionIterator.java b/graphics/java/android/graphics/RegionIterator.java index 443b23c1b5fc..5d74487e5a8e 100644 --- a/graphics/java/android/graphics/RegionIterator.java +++ b/graphics/java/android/graphics/RegionIterator.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class RegionIterator { /** diff --git a/graphics/java/android/graphics/RenderEffect.java b/graphics/java/android/graphics/RenderEffect.java index b8a46856601e..06bfb82ef630 100644 --- a/graphics/java/android/graphics/RenderEffect.java +++ b/graphics/java/android/graphics/RenderEffect.java @@ -30,6 +30,7 @@ import libcore.util.NativeAllocationRegistry; * Additionally a {@link RenderEffect} can be applied to a View's backing RenderNode through * {@link android.view.View#setRenderEffect(RenderEffect)} */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class RenderEffect { private static class RenderEffectHolder { diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 03a8b306f99d..fa41876187cf 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -192,6 +192,7 @@ import java.lang.ref.WeakReference; * top-level content is desired, and finally calling {@link Surface#unlockCanvasAndPost(Canvas)}. * </p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class RenderNode { // Use a Holder to allow static initialization in the boot image. diff --git a/graphics/java/android/graphics/RuntimeColorFilter.java b/graphics/java/android/graphics/RuntimeColorFilter.java index a64acfe767a9..06aecc3f2c49 100644 --- a/graphics/java/android/graphics/RuntimeColorFilter.java +++ b/graphics/java/android/graphics/RuntimeColorFilter.java @@ -37,6 +37,7 @@ import com.android.graphics.hwui.flags.Flags; * </pre> */ @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class RuntimeColorFilter extends ColorFilter { private String mAgsl; diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index db2376e008f5..6464f72490c4 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -248,6 +248,7 @@ import libcore.util.NativeAllocationRegistry; * the bitmap), remember that the coordinates are local to the canvas.</p> * */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class RuntimeShader extends Shader { private static class NoImagePreloadHolder { diff --git a/graphics/java/android/graphics/RuntimeXfermode.java b/graphics/java/android/graphics/RuntimeXfermode.java index c8a0b1a11339..1e20bd352244 100644 --- a/graphics/java/android/graphics/RuntimeXfermode.java +++ b/graphics/java/android/graphics/RuntimeXfermode.java @@ -39,6 +39,7 @@ import libcore.util.NativeAllocationRegistry; * </pre> */ @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class RuntimeXfermode extends Xfermode { private static class NoImagePreloadHolder { diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java index 4d6beadc0fdd..369fab45bf69 100644 --- a/graphics/java/android/graphics/Shader.java +++ b/graphics/java/android/graphics/Shader.java @@ -29,6 +29,7 @@ import libcore.util.NativeAllocationRegistry; * paint.setShader(shader). After that any object (other than a bitmap) that is * drawn with that paint will get its color(s) from the shader. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Shader { private static class NoImagePreloadHolder { diff --git a/graphics/java/android/graphics/SumPathEffect.java b/graphics/java/android/graphics/SumPathEffect.java index 8fedc317c428..3543e101fb38 100644 --- a/graphics/java/android/graphics/SumPathEffect.java +++ b/graphics/java/android/graphics/SumPathEffect.java @@ -16,6 +16,7 @@ package android.graphics; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SumPathEffect extends PathEffect { /** diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 5caedba364be..df384ead58fb 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -78,6 +78,7 @@ import java.lang.ref.WeakReference; * frame-available callback is called on an arbitrary thread, so unless special care is taken {@link * #updateTexImage} should not be called directly from the callback. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SurfaceTexture { private final Looper mCreatorLooper; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java index 3a29395b1717..94219259a69d 100644 --- a/graphics/java/android/graphics/SweepGradient.java +++ b/graphics/java/android/graphics/SweepGradient.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class SweepGradient extends Shader { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private float mCx; diff --git a/graphics/java/android/graphics/TableMaskFilter.java b/graphics/java/android/graphics/TableMaskFilter.java index 204f9705852a..ca7627c40031 100644 --- a/graphics/java/android/graphics/TableMaskFilter.java +++ b/graphics/java/android/graphics/TableMaskFilter.java @@ -21,6 +21,7 @@ import android.compat.annotation.UnsupportedAppUsage; /** * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TableMaskFilter extends MaskFilter { public TableMaskFilter(byte[] table) { diff --git a/graphics/java/android/graphics/TemporaryBuffer.java b/graphics/java/android/graphics/TemporaryBuffer.java index ef3f7f704e0d..681c48ea9f71 100644 --- a/graphics/java/android/graphics/TemporaryBuffer.java +++ b/graphics/java/android/graphics/TemporaryBuffer.java @@ -23,6 +23,7 @@ import com.android.internal.util.ArrayUtils; /** * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TemporaryBuffer { @UnsupportedAppUsage public static char[] obtain(int len) { diff --git a/graphics/java/android/graphics/TextureLayer.java b/graphics/java/android/graphics/TextureLayer.java index ac1bd6902062..981b78a7b5b8 100644 --- a/graphics/java/android/graphics/TextureLayer.java +++ b/graphics/java/android/graphics/TextureLayer.java @@ -29,6 +29,7 @@ import com.android.internal.util.VirtualRefBasePtr; * * @hide TODO: Make this a SystemApi for b/155905258 */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class TextureLayer implements AutoCloseable { private HardwareRenderer mRenderer; private VirtualRefBasePtr mFinalizer; diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 874b847c709c..d1aca34c7b8d 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -42,6 +42,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.provider.FontRequest; import android.provider.FontsContract; +import android.ravenwood.annotation.RavenwoodReplace; import android.system.ErrnoException; import android.system.OsConstants; import android.text.FontConfig; @@ -86,6 +87,7 @@ import java.util.Objects; * textSize, textSkewX, textScaleX to specify * how text appears when drawn (and measured). */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Typeface { private static String TAG = "Typeface"; @@ -93,9 +95,11 @@ public class Typeface { /** @hide */ public static final boolean ENABLE_LAZY_TYPEFACE_INITIALIZATION = true; - private static final NativeAllocationRegistry sRegistry = - NativeAllocationRegistry.createMalloced( - Typeface.class.getClassLoader(), nativeGetReleaseFunc()); + private static class NoImagePreloadHolder { + static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + Typeface.class.getClassLoader(), nativeGetReleaseFunc()); + } /** The default NORMAL typeface object */ public static final Typeface DEFAULT = null; @@ -1284,7 +1288,7 @@ public class Typeface { } native_instance = ni; - mCleaner = sRegistry.registerNativeAllocation(this, native_instance); + mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance); mStyle = nativeGetStyle(ni); mWeight = nativeGetWeight(ni); mIsVariationInstance = nativeIsVariationInstance(ni); @@ -1560,9 +1564,23 @@ public class Typeface { } static { + staticInitializer(); + } + + @RavenwoodReplace(reason = "Prevent circular reference on host side JVM", bug = 337329128) + private static void staticInitializer() { + init(); + } + + private static void staticInitializer$ravenwood() { + /* no-op */ + } + + /** @hide */ + public static void init() { // Preload Roboto-Regular.ttf in Zygote for improving app launch performance. - preloadFontFile("/system/fonts/Roboto-Regular.ttf"); - preloadFontFile("/system/fonts/RobotoStatic-Regular.ttf"); + preloadFontFile(SystemFonts.SYSTEM_FONT_DIR + "Roboto-Regular.ttf"); + preloadFontFile(SystemFonts.SYSTEM_FONT_DIR + "RobotoStatic-Regular.ttf"); String locale = SystemProperties.get("persist.sys.locale", "en-US"); String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript(); @@ -1642,6 +1660,21 @@ public class Typeface { setSystemFontMap(typefaceMap); } + /** + * {@link #loadPreinstalledSystemFontMap()} does not actually initialize the native + * system font APIs. Add a new method to actually load the font files without going + * through SharedMemory. + * + * @hide + */ + public static void loadNativeSystemFonts() { + synchronized (SYSTEM_FONT_MAP_LOCK) { + for (var type : sSystemFontMap.values()) { + nativeAddFontCollections(type.native_instance); + } + } + } + static { if (!ENABLE_LAZY_TYPEFACE_INITIALIZATION) { loadPreinstalledSystemFontMap(); diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java index fb689e4cb9c2..eda9e3c1055d 100644 --- a/graphics/java/android/graphics/Xfermode.java +++ b/graphics/java/android/graphics/Xfermode.java @@ -28,4 +28,5 @@ package android.graphics; * specified in the Modes enum. When an Xfermode is assigned to a Paint, then * objects drawn with that paint have the xfermode applied. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Xfermode {} diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java index b0c7f202f23a..2b7f40493e8d 100644 --- a/graphics/java/android/graphics/YuvImage.java +++ b/graphics/java/android/graphics/YuvImage.java @@ -32,6 +32,7 @@ import java.io.OutputStream; * To compress a rectangle region in the YUV data, users have to specify the * region by left, top, width and height. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class YuvImage { /** diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java index 2893177aafc5..8be340005543 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -44,6 +44,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.Collections; @@ -54,6 +55,7 @@ import java.util.Set; /** * A font class can be used for creating FontFamily. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Font { private static final String TAG = "Font"; @@ -293,7 +295,14 @@ public final class Font { int capacity = assetStream.available(); ByteBuffer buffer = ByteBuffer.allocateDirect(capacity); buffer.order(ByteOrder.nativeOrder()); - assetStream.read(buffer.array(), buffer.arrayOffset(), assetStream.available()); + if (buffer.hasArray()) { + assetStream.read(buffer.array(), buffer.arrayOffset(), assetStream.available()); + } else { + // Direct buffer does not have a backing array on Ravenwood, + // wrap it with a channel and read from it + var ch = Channels.newChannel(assetStream); + ch.read(buffer.duplicate()); + } if (assetStream.read() != -1) { throw new IOException("Unable to access full contents of " + path); diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index b7bf0553bcc6..732a5f3bfce4 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -43,6 +43,7 @@ import java.util.Map; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class FontCustomizationParser { private static final String TAG = "FontCustomizationParser"; diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index 5a7b0bbca399..0ab46398c924 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -66,6 +66,7 @@ import java.util.Set; * </p> * */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class FontFamily { private static final String TAG = "FontFamily"; diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java index abcafb666576..305ab3b39995 100644 --- a/graphics/java/android/graphics/fonts/FontFileUtil.java +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -32,6 +32,7 @@ import java.util.Set; * Provides a utility for font file operations. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class FontFileUtil { private FontFileUtil() {} // Do not instantiate diff --git a/graphics/java/android/graphics/fonts/FontStyle.java b/graphics/java/android/graphics/fonts/FontStyle.java index 48969aa71059..b3ddbed645ff 100644 --- a/graphics/java/android/graphics/fonts/FontStyle.java +++ b/graphics/java/android/graphics/fonts/FontStyle.java @@ -44,6 +44,7 @@ import java.util.Objects; * </p> * */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class FontStyle { private static final String TAG = "FontStyle"; diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java index 30a248bb3e0e..1d715940d628 100644 --- a/graphics/java/android/graphics/fonts/FontVariationAxis.java +++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java @@ -31,6 +31,7 @@ import java.util.regex.Pattern; /** * Class that holds information about single font variation axis. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class FontVariationAxis { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int mTag; diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 0e25c346064c..599c42659ece 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.graphics.FontListParser; import android.graphics.Typeface; import android.os.LocaleList; +import android.ravenwood.annotation.RavenwoodReplace; import android.text.FontConfig; import android.util.ArrayMap; import android.util.Log; @@ -32,6 +33,7 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.ravenwood.RavenwoodEnvironment; import org.xmlpull.v1.XmlPullParserException; @@ -50,23 +52,46 @@ import java.util.Set; /** * Provides the system font configurations. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class SystemFonts { private static final String TAG = "SystemFonts"; - private static final String FONTS_XML = "/system/etc/font_fallback.xml"; - private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml"; + private static final String FONTS_XML = getFontsXmlDir() + "font_fallback.xml"; + /** @hide */ + public static final String LEGACY_FONTS_XML = getFontsXmlDir() + "fonts.xml"; /** @hide */ - public static final String SYSTEM_FONT_DIR = "/system/fonts/"; + public static final String SYSTEM_FONT_DIR = getSystemFontDir(); private static final String OEM_XML = "/product/etc/fonts_customization.xml"; /** @hide */ public static final String OEM_FONT_DIR = "/product/fonts/"; + private static final String DEVICE_FONTS_XML_DIR = "/system/etc/"; + private static final String DEVICE_FONT_DIR = "/system/fonts/"; + private SystemFonts() {} // Do not instansiate. private static final Object LOCK = new Object(); private static @GuardedBy("sLock") Set<Font> sAvailableFonts; + @RavenwoodReplace + private static String getFontsXmlDir() { + return DEVICE_FONTS_XML_DIR; + } + + private static String getFontsXmlDir$ravenwood() { + return RavenwoodEnvironment.getInstance().getRavenwoodRuntimePath() + "fonts/"; + } + + @RavenwoodReplace + private static String getSystemFontDir() { + return DEVICE_FONT_DIR; + } + + private static String getSystemFontDir$ravenwood() { + return RavenwoodEnvironment.getInstance().getRavenwoodRuntimePath() + "fonts/"; + } + /** * Returns all available font files in the system. * diff --git a/graphics/java/android/graphics/text/GraphemeBreak.java b/graphics/java/android/graphics/text/GraphemeBreak.java index f82b2fd659cc..0bc1e3b0cae3 100644 --- a/graphics/java/android/graphics/text/GraphemeBreak.java +++ b/graphics/java/android/graphics/text/GraphemeBreak.java @@ -17,6 +17,7 @@ package android.graphics.text; /** @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class GraphemeBreak { private GraphemeBreak() { } diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index 5a1086cef407..fa3cfbdd4e97 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -24,12 +24,13 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; -import android.app.ActivityThread; import android.os.Build; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; +import dalvik.system.VMRuntime; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -41,6 +42,7 @@ import java.util.Objects; * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external"> * line-break property</a> for more information. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class LineBreakConfig implements Parcelable { /** * No hyphenation preference is specified. @@ -484,8 +486,7 @@ public final class LineBreakConfig implements Parcelable { * @hide */ public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) { - final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo() - .targetSdkVersion; + final int targetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion(); final int defaultStyle; final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM; if (targetSdkVersion >= vicVersion) { @@ -519,8 +520,7 @@ public final class LineBreakConfig implements Parcelable { */ public static @LineBreakWordStyle int getResolvedLineBreakWordStyle( @Nullable LineBreakConfig config) { - final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo() - .targetSdkVersion; + final int targetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion(); final int defaultWordStyle; final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM; if (targetSdkVersion >= vicVersion) { diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java index 94de066c9182..29135b837352 100644 --- a/graphics/java/android/graphics/text/LineBreaker.java +++ b/graphics/java/android/graphics/text/LineBreaker.java @@ -91,6 +91,7 @@ import java.lang.annotation.RetentionPolicy; * </pre> * </p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LineBreaker { /** @hide */ @IntDef(prefix = { "BREAK_STRATEGY_" }, value = { diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 884268a4b85c..f58d5311fa30 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -56,6 +56,7 @@ import java.util.Objects; * </pre> * </p> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class MeasuredText { private static final String TAG = "MeasuredText"; diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java index 43216ba6e087..31125ffa7bd4 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -46,6 +46,7 @@ import java.util.Objects; * @see TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint) * @see TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint) */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class PositionedGlyphs { private static class NoImagePreloadHolder { private static final NativeAllocationRegistry REGISTRY = diff --git a/graphics/java/android/graphics/text/TextRunShaper.java b/graphics/java/android/graphics/text/TextRunShaper.java index 19ea04a48046..f1e3d67e9925 100644 --- a/graphics/java/android/graphics/text/TextRunShaper.java +++ b/graphics/java/android/graphics/text/TextRunShaper.java @@ -40,6 +40,7 @@ import com.android.internal.util.Preconditions; * @see android.text.TextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint, * TextShaper.GlyphsConsumer) */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class TextRunShaper { private TextRunShaper() {} // Do not instantiate diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index eb59d6efdeff..7ab9e2e65b76 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -705,8 +705,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } private static boolean isOverlayTransitionSupported() { - return Flags.moveAnimationOptionsToChange() - && Flags.activityEmbeddingOverlayPresentationFlag(); + return Flags.activityEmbeddingOverlayPresentationFlag(); } @NonNull diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index a08f88a5b937..1e72d64397d7 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -184,3 +184,13 @@ flag { description: "Try out bubble bar on phones" bug: "394869612" } + +flag { + name: "enable_bubble_task_view_listener" + namespace: "multitasking" + description: "Use the same taskview listener for bubble bar and floating" + bug: "272102927" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml index 95cd1c72a2af..800ea7446b6e 100644 --- a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml +++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml @@ -2,6 +2,7 @@ package="com.android.wm.shell.multivalenttests"> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/> + <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/> <application android:debuggable="true" android:supportsRtl="true" > <uses-library android:name="android.test.runner" /> diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt index 9ebc3d78b3a7..3aefcd5ec6c0 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.bubbles +import android.app.ActivityOptions import android.app.Notification import android.app.PendingIntent import android.content.ComponentName @@ -24,6 +25,8 @@ import android.content.Intent import android.content.pm.ShortcutInfo import android.graphics.drawable.Icon import android.os.UserHandle +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.service.notification.NotificationListenerService.Ranking import android.service.notification.StatusBarNotification import android.view.View @@ -33,6 +36,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_ANYTHING import com.android.wm.shell.R import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener import com.android.wm.shell.common.TestShellExecutor @@ -41,6 +45,7 @@ import com.android.wm.shell.taskview.TaskViewController import com.android.wm.shell.taskview.TaskViewTaskController import com.google.common.truth.Truth.assertThat import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito @@ -48,6 +53,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -61,6 +67,9 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class BubbleTaskViewListenerTest { + @get:Rule + val setFlagsRule = SetFlagsRule() + private val context = ApplicationProvider.getApplicationContext<Context>() private var taskViewController = mock<TaskViewController>() @@ -155,9 +164,22 @@ class BubbleTaskViewListenerTest { } getInstrumentation().waitForIdleSync() - // ..so it's pending intent-based, and launches that + // ..so it's pending intent-based, so the pending intent should be active assertThat(b.isPendingIntentActive).isTrue() - verify(taskViewController).startActivity(any(), eq(pendingIntent), any(), any(), any()) + + val intentCaptor = argumentCaptor<Intent>() + val optionsCaptor = argumentCaptor<ActivityOptions>() + + verify(taskViewController).startActivity(any(), + eq(pendingIntent), + intentCaptor.capture(), + optionsCaptor.capture(), + any()) + val intentFlags = intentCaptor.lastValue.flags + assertThat((intentFlags and Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0).isTrue() + assertThat((intentFlags and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() + assertThat(optionsCaptor.lastValue.launchedFromBubble).isTrue() + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() } @Test @@ -178,12 +200,52 @@ class BubbleTaskViewListenerTest { } getInstrumentation().waitForIdleSync() - assertThat(b.isPendingIntentActive).isFalse() - verify(taskViewController).startShortcutActivity(any(), eq(shortcutInfo), any(), any()) + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // not triggered for shortcut chats + verify(taskViewController).startShortcutActivity(any(), + eq(shortcutInfo), + optionsCaptor.capture(), + any()) + assertThat(optionsCaptor.lastValue.launchedFromBubble).isTrue() + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isTrue() + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() } + @EnableFlags(FLAG_ENABLE_BUBBLE_ANYTHING) @Test - fun onInitialized_appBubble() { + fun onInitialized_shortcutBubble() { + val shortcutInfo = ShortcutInfo.Builder(context) + .setId("mockShortcutId") + .build() + + val b = createShortcutBubble(shortcutInfo) + bubbleTaskViewListener.setBubble(b) + + assertThat(b.isChat).isFalse() + assertThat(b.isShortcut).isTrue() + assertThat(b.shortcutInfo).isNotNull() + + getInstrumentation().runOnMainSync { + bubbleTaskViewListener.onInitialized() + } + getInstrumentation().waitForIdleSync() + + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active + verify(taskViewController).startShortcutActivity(any(), + eq(shortcutInfo), + optionsCaptor.capture(), + any()) + assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyMultipleTaskFlagForShortcut).isTrue() + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() + } + + @Test + fun onInitialized_appBubble_intent() { val b = createAppBubble() bubbleTaskViewListener.setBubble(b) @@ -194,11 +256,83 @@ class BubbleTaskViewListenerTest { } getInstrumentation().waitForIdleSync() - assertThat(b.isPendingIntentActive).isFalse() - verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any()) + val intentCaptor = argumentCaptor<Intent>() + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active + verify(taskViewController).startActivity(any(), + any(), + intentCaptor.capture(), + optionsCaptor.capture(), + any()) + + assertThat((intentCaptor.lastValue.flags + and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() + assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() } @Test + fun onInitialized_appBubble_pendingIntent() { + val b = createAppBubble(usePendingIntent = true) + bubbleTaskViewListener.setBubble(b) + + assertThat(b.isApp).isTrue() + + getInstrumentation().runOnMainSync { + bubbleTaskViewListener.onInitialized() + } + getInstrumentation().waitForIdleSync() + + val intentCaptor = argumentCaptor<Intent>() + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active + verify(taskViewController).startActivity(any(), + any(), + intentCaptor.capture(), + optionsCaptor.capture(), + any()) + + assertThat((intentCaptor.lastValue.flags + and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() + assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() + } + + @Test + fun onInitialized_noteBubble() { + val b = createNoteBubble() + bubbleTaskViewListener.setBubble(b) + + assertThat(b.isNote).isTrue() + + getInstrumentation().runOnMainSync { + bubbleTaskViewListener.onInitialized() + } + getInstrumentation().waitForIdleSync() + + val intentCaptor = argumentCaptor<Intent>() + val optionsCaptor = argumentCaptor<ActivityOptions>() + + assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active + verify(taskViewController).startActivity(any(), + any(), + intentCaptor.capture(), + optionsCaptor.capture(), + any()) + + assertThat((intentCaptor.lastValue.flags + and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() + assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only + assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only + assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() + } + + + @Test fun onInitialized_preparingTransition() { val b = createAppBubble() bubbleTaskViewListener.setBubble(b) @@ -416,13 +550,24 @@ class BubbleTaskViewListenerTest { assertThat(isNew).isTrue() } - private fun createAppBubble(): Bubble { + private fun createAppBubble(usePendingIntent: Boolean = false): Bubble { val target = Intent(context, TestActivity::class.java) target.setPackage(context.packageName) + if (usePendingIntent) { + // Robolectric doesn't seem to play nice with PendingIntents, have to mock it. + val pendingIntent = mock<PendingIntent>() + whenever(pendingIntent.intent).thenReturn(target) + return Bubble.createAppBubble(pendingIntent, mock<UserHandle>(), + mainExecutor, bgExecutor) + } return Bubble.createAppBubble(target, mock<UserHandle>(), mock<Icon>(), mainExecutor, bgExecutor) } + private fun createShortcutBubble(shortcutInfo: ShortcutInfo): Bubble { + return Bubble.createShortcutBubble(shortcutInfo, mainExecutor, bgExecutor) + } + private fun createNoteBubble(): Bubble { val target = Intent(context, TestActivity::class.java) target.setPackage(context.packageName) diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index f5f3f0fe52eb..a0c68ad44379 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -670,7 +670,4 @@ <dimen name="desktop_windowing_education_promo_height">352dp</dimen> <!-- The corner radius of the desktop windowing education promo. --> <dimen name="desktop_windowing_education_promo_corner_radius">28dp</dimen> - - <!-- The corner radius of freeform tasks in desktop windowing. --> - <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml index 11a6f32d7454..23c9caf2046c 100644 --- a/libs/WindowManager/Shell/shared/res/values/dimen.xml +++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml @@ -46,4 +46,7 @@ <dimen name="drop_target_expanded_view_height">578</dimen> <dimen name="drop_target_expanded_view_padding_bottom">108</dimen> <dimen name="drop_target_expanded_view_padding_horizontal">24</dimen> + + <!-- The corner radius of freeform tasks in desktop windowing. --> + <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen> </resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java index e92c1eb81e89..43dd9b73b352 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java @@ -74,6 +74,12 @@ public class Interpolators { 0.05f, 0.7f, 0.1f, 1f); /** + * The standard accelerating interpolator that should be used on every regular movement of + * content that is disappearing e.g. when moving off screen. + */ + public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f); + + /** * The standard decelerating interpolator that should be used on every regular movement of * content that is appearing e.g. when coming from off screen. */ diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt index 0586e265eced..4ecace0292cf 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt @@ -19,52 +19,76 @@ package com.android.wm.shell.shared.animation import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator -import android.util.DisplayMetrics +import android.content.Context +import android.os.Handler +import android.view.Choreographer import android.view.SurfaceControl.Transaction -import android.view.animation.LinearInterpolator -import android.view.animation.PathInterpolator import android.window.TransitionInfo.Change +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW +import com.android.internal.jank.InteractionJankMonitor /** Creates minimization animation */ object MinimizeAnimator { private const val MINIMIZE_ANIM_ALPHA_DURATION_MS = 100L - private val STANDARD_ACCELERATE = PathInterpolator(0.3f, 0f, 1f, 1f) - private val minimizeBoundsAnimationDef = WindowAnimator.BoundsAnimationParams( durationMs = 200, endOffsetYDp = 12f, endScale = 0.97f, - interpolator = STANDARD_ACCELERATE, + interpolator = Interpolators.STANDARD_ACCELERATE, ) + /** + * Creates a minimize animator for given task [Change]. + * + * @param onAnimFinish finish-callback for the animation, note that this is called on the same + * thread as the animation itself. + * @param animationHandler the Handler that the animation is running on. + */ @JvmStatic fun create( - displayMetrics: DisplayMetrics, + context: Context, change: Change, transaction: Transaction, onAnimFinish: (Animator) -> Unit, + interactionJankMonitor: InteractionJankMonitor, + animationHandler: Handler, ): Animator { val boundsAnimator = WindowAnimator.createBoundsAnimator( - displayMetrics, + context.resources.displayMetrics, minimizeBoundsAnimationDef, change, transaction, ) val alphaAnimator = ValueAnimator.ofFloat(1f, 0f).apply { duration = MINIMIZE_ANIM_ALPHA_DURATION_MS - interpolator = LinearInterpolator() + interpolator = Interpolators.LINEAR addUpdateListener { animation -> - transaction.setAlpha(change.leash, animation.animatedValue as Float).apply() + transaction + .setAlpha(change.leash, animation.animatedValue as Float) + .setFrameTimeline(Choreographer.getInstance().vsyncId) + .apply() } } val listener = object : Animator.AnimatorListener { - override fun onAnimationEnd(animator: Animator) = onAnimFinish(animator) - override fun onAnimationCancel(animator: Animator) = Unit + override fun onAnimationStart(animator: Animator) { + interactionJankMonitor.begin( + change.leash, + context, + animationHandler, + CUJ_DESKTOP_MODE_MINIMIZE_WINDOW, + ) + } + override fun onAnimationCancel(animator: Animator) { + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) + } override fun onAnimationRepeat(animator: Animator) = Unit - override fun onAnimationStart(animator: Animator) = Unit + override fun onAnimationEnd(animator: Animator) { + interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) + onAnimFinish(animator) + } } return AnimatorSet().apply { playTogether(boundsAnimator, alphaAnimator) diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt index 14338a49ee2f..0e4a6b9fb083 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt @@ -16,12 +16,14 @@ package com.android.wm.shell.shared.desktopmode +import android.Manifest.permission.SYSTEM_ALERT_WINDOW import android.app.TaskInfo import android.content.Context import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS +import android.content.pm.PackageManager import android.window.DesktopModeFlags import com.android.internal.R @@ -32,8 +34,10 @@ import com.android.internal.R class DesktopModeCompatPolicy(private val context: Context) { private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi) + private val pkgManager: PackageManager + get() = context.getPackageManager() private val defaultHomePackage: String? - get() = context.getPackageManager().getHomeActivities(ArrayList())?.packageName + get() = pkgManager.getHomeActivities(ArrayList())?.packageName /** * If the top activity should be exempt from desktop windowing and forced back to fullscreen. @@ -47,11 +51,12 @@ class DesktopModeCompatPolicy(private val context: Context) { fun isTopActivityExemptFromDesktopWindowing(packageName: String?, numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) = - DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue - && ((isSystemUiTask(packageName) - || isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) - || isTransparentTask(isActivityStackTransparent, numActivities)) - && !isTopActivityNoDisplay) + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue && + ((isSystemUiTask(packageName) || + isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) || + (isTransparentTask(isActivityStackTransparent, numActivities) && + hasFullscreenTransparentPermission(packageName))) && + !isTopActivityNoDisplay) /** * Whether the caption insets should be excluded from configuration for system to handle. @@ -83,6 +88,26 @@ class DesktopModeCompatPolicy(private val context: Context) { private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage + // Checks if the app for the given package has the SYSTEM_ALERT_WINDOW permission. + private fun hasFullscreenTransparentPermission(packageName: String?): Boolean { + if (DesktopModeFlags.ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS.isTrue) { + if (packageName == null) { + return false + } + return try { + val packageInfo = pkgManager.getPackageInfo( + packageName, + PackageManager.GET_PERMISSIONS + ) + packageInfo?.requestedPermissions?.contains(SYSTEM_ALERT_WINDOW) == true + } catch (e: PackageManager.NameNotFoundException) { + false // Package not found + } + } + // If the flag is disabled we make this condition neutral. + return true + } + /** * Returns true if the tasks base activity is part of the default home package, or there is * currently no default home package available. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index f269b3831aab..78f5154c0ecb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -364,7 +364,7 @@ class ActivityEmbeddingAnimationRunner { @NonNull SurfaceControl.Transaction finishTransaction, @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { for (ActivityEmbeddingAnimationAdapter adapter : adapters) { - final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange, + final int backgroundColor = getTransitionBackgroundColorIfSet(adapter.mChange, adapter.mAnimation, 0 /* defaultColor */); if (backgroundColor != 0) { // We only need to show one color. @@ -436,8 +436,8 @@ class ActivityEmbeddingAnimationRunner { final TransitionInfo.AnimationOptions options = boundsAnimationChange .getAnimationOptions(); if (options != null) { - final Animation overrideAnimation = mAnimationSpec.loadCustomAnimationFromOptions( - options, TRANSIT_CHANGE); + final Animation overrideAnimation = + mAnimationSpec.loadCustomAnimation(options, TRANSIT_CHANGE); if (overrideAnimation != null) { overrideShowBackdrop = overrideAnimation.getShowBackdrop(); } @@ -447,7 +447,7 @@ class ActivityEmbeddingAnimationRunner { // There are two animations in the array. The first one is for the start leash // (snapshot), and the second one is for the end leash (TaskFragment). final Animation[] animations = - mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds); + mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds); // Jump cut if either animation has zero for duration. for (Animation animation : animations) { if (shouldUseJumpCutForAnimation(animation)) { @@ -500,12 +500,10 @@ class ActivityEmbeddingAnimationRunner { // window without bounds change. animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); } else if (TransitionUtil.isClosingType(change.getMode())) { - animation = - mAnimationSpec.createChangeBoundsCloseAnimation(info, change, parentBounds); + animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds); shouldShowBackgroundColor = false; } else { - animation = - mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds); + animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds); shouldShowBackgroundColor = false; } if (shouldUseJumpCutForAnimation(animation)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index 77799e99607b..2b9eda40cdbb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -42,7 +42,6 @@ import android.view.animation.TranslateAnimation; import android.window.TransitionInfo; import com.android.internal.policy.TransitionAnimation; -import com.android.window.flags.Flags; import com.android.wm.shell.shared.TransitionUtil; /** Animation spec for ActivityEmbedding transition. */ @@ -94,9 +93,10 @@ class ActivityEmbeddingAnimationSpec { /** Animation for window that is opening in a change transition. */ @NonNull - Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info, - @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { - final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change, + @NonNull Rect parentBounds) { + final Animation customAnimation = + loadCustomAnimation(change.getAnimationOptions(), TRANSIT_CHANGE); if (customAnimation != null) { return customAnimation; } @@ -126,9 +126,10 @@ class ActivityEmbeddingAnimationSpec { /** Animation for window that is closing in a change transition. */ @NonNull - Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info, - @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { - final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change, + @NonNull Rect parentBounds) { + final Animation customAnimation = + loadCustomAnimation(change.getAnimationOptions(), TRANSIT_CHANGE); if (customAnimation != null) { return customAnimation; } @@ -162,12 +163,13 @@ class ActivityEmbeddingAnimationSpec { * the second one is for the end leash. */ @NonNull - Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info, - @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { + Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change, + @NonNull Rect parentBounds) { // TODO(b/293658614): Support more complicated animations that may need more than a noop // animation as the start leash. final Animation noopAnimation = createNoopAnimation(change); - final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + final Animation customAnimation = + loadCustomAnimation(change.getAnimationOptions(), TRANSIT_CHANGE); if (customAnimation != null) { return new Animation[]{noopAnimation, customAnimation}; } @@ -221,7 +223,8 @@ class ActivityEmbeddingAnimationSpec { Animation loadOpenAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); - final Animation customAnimation = loadCustomAnimation(info, change, change.getMode()); + final Animation customAnimation = + loadCustomAnimation(change.getAnimationOptions(), change.getMode()); final Animation animation; if (customAnimation != null) { animation = customAnimation; @@ -248,7 +251,8 @@ class ActivityEmbeddingAnimationSpec { Animation loadCloseAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); - final Animation customAnimation = loadCustomAnimation(info, change, change.getMode()); + final Animation customAnimation = + loadCustomAnimation(change.getAnimationOptions(), change.getMode()); final Animation animation; if (customAnimation != null) { animation = customAnimation; @@ -280,20 +284,8 @@ class ActivityEmbeddingAnimationSpec { } @Nullable - private Animation loadCustomAnimation(@NonNull TransitionInfo info, - @NonNull TransitionInfo.Change change, @WindowManager.TransitionType int mode) { - final TransitionInfo.AnimationOptions options; - if (Flags.moveAnimationOptionsToChange()) { - options = change.getAnimationOptions(); - } else { - options = info.getAnimationOptions(); - } - return loadCustomAnimationFromOptions(options, mode); - } - - @Nullable - Animation loadCustomAnimationFromOptions(@Nullable TransitionInfo.AnimationOptions options, - @WindowManager.TransitionType int mode) { + Animation loadCustomAnimation(@Nullable TransitionInfo.AnimationOptions options, + @WindowManager.TransitionType int mode) { if (options == null || options.getType() != ANIM_CUSTOM) { return null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 55ed5fa4b56f..3a95333309ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -40,7 +40,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.window.flags.Flags; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -123,9 +122,6 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle } private boolean shouldAnimateAnimationOptions(@NonNull TransitionInfo info) { - if (!Flags.moveAnimationOptionsToChange()) { - return shouldAnimateAnimationOptions(info.getAnimationOptions()); - } for (TransitionInfo.Change change : info.getChanges()) { if (!shouldAnimateAnimationOptions(change.getAnimationOptions())) { // If any of override animation is not supported, don't animate the transition. 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 d9489287ff42..313d151aeab7 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 @@ -364,7 +364,7 @@ public class Bubble implements BubbleViewProvider { @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { return new Bubble(intent, user, - /* key= */ getAppBubbleKeyForApp(ComponentUtils.getPackageName(intent), user), + /* key= */ getAppBubbleKeyForApp(intent.getIntent().getPackage(), user), mainExecutor, bgExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 3f607a9c52ef..2c2451cab999 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -197,6 +197,8 @@ public class BubbleExpandedView extends LinearLayout { */ private final FrameLayout mExpandedViewContainer = new FrameLayout(getContext()); + private TaskView.Listener mCurrentTaskViewListener; + private final TaskView.Listener mTaskViewListener = new TaskView.Listener() { private boolean mInitialized = false; private boolean mDestroyed = false; @@ -235,18 +237,24 @@ public class BubbleExpandedView extends LinearLayout { Context context = mContext.createContextAsUser( mBubble.getUser(), Context.CONTEXT_RESTRICTED); + Intent fillInIntent = new Intent(); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); PendingIntent pi = PendingIntent.getActivity( context, /* requestCode= */ 0, - mBubble.getIntent().addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, + mBubble.getIntent(), + // Needs to be mutable for the fillInIntent + PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, /* options= */ null); - mTaskView.startActivity(pi, /* fillInIntent= */ null, options, - launchBounds); + mTaskView.startActivity(pi, fillInIntent, options, launchBounds); } else if (!mIsOverflow && isShortcutBubble) { ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey()); - options.setLaunchedFromBubble(true); - options.setApplyActivityFlagsForBubbles(true); + if (mBubble.isChat()) { + options.setLaunchedFromBubble(true); + options.setApplyActivityFlagsForBubbles(true); + } else { + options.setApplyMultipleTaskFlagForShortcut(true); + } mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); } else { @@ -453,7 +461,34 @@ public class BubbleExpandedView extends LinearLayout { mTaskView = bubbleTaskView.getTaskView(); // reset the insets that might left after TaskView is shown in BubbleBarExpandedView mTaskView.setCaptionInsets(null); - bubbleTaskView.setDelegateListener(mTaskViewListener); + if (Flags.enableBubbleTaskViewListener()) { + mCurrentTaskViewListener = new BubbleTaskViewListener(mContext, bubbleTaskView, + /* viewParent= */ this, expandedViewManager, + new BubbleTaskViewListener.Callback() { + @Override + public void onTaskCreated() { + setContentVisibility(true); + } + + @Override + public void onContentVisibilityChanged(boolean visible) { + setContentVisibility(visible); + } + + @Override + public void onBackPressed() { + mStackView.onBackPressed(); + } + + @Override + public void onTaskRemovalStarted() { + // nothing to do / handled in listener. + } + }); + } else { + mCurrentTaskViewListener = mTaskViewListener; + bubbleTaskView.setDelegateListener(mCurrentTaskViewListener); + } // set a fixed width so it is not recalculated as part of a rotation. the width will be // updated manually after the rotation. @@ -464,9 +499,12 @@ public class BubbleExpandedView extends LinearLayout { } mExpandedViewContainer.addView(mTaskView, lp); bringChildToFront(mTaskView); - if (bubbleTaskView.isCreated()) { - mTaskViewListener.onTaskCreated( - bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName()); + + if (!Flags.enableBubbleTaskViewListener()) { + if (bubbleTaskView.isCreated()) { + mCurrentTaskViewListener.onTaskCreated( + bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName()); + } } } } @@ -897,7 +935,12 @@ public class BubbleExpandedView extends LinearLayout { Log.w(TAG, "Stack is null for bubble: " + bubble); return; } - boolean isNew = mBubble == null || didBackingContentChange(bubble); + boolean isNew; + if (mCurrentTaskViewListener instanceof BubbleTaskViewListener) { + isNew = ((BubbleTaskViewListener) mCurrentTaskViewListener).setBubble(bubble); + } else { + isNew = mBubble == null || didBackingContentChange(bubble); + } boolean isUpdate = bubble != null && mBubble != null && bubble.getKey().equals(mBubble.getKey()); ProtoLog.d(WM_SHELL_BUBBLES, "BubbleExpandedView - update bubble=%s; isNew=%b; isUpdate=%b", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java index a38debb702dc..63d713495177 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java @@ -129,27 +129,28 @@ public class BubbleTaskViewListener implements TaskView.Listener { Context context = mContext.createContextAsUser( mBubble.getUser(), Context.CONTEXT_RESTRICTED); - Intent fillInIntent = null; + Intent fillInIntent = new Intent(); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); // First try get pending intent from the bubble PendingIntent pi = mBubble.getPendingIntent(); if (pi == null) { - // If null - create new one + // If null - create new one based on the bubble intent pi = PendingIntent.getActivity( context, /* requestCode= */ 0, - mBubble.getIntent() - .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), - PendingIntent.FLAG_IMMUTABLE - | PendingIntent.FLAG_UPDATE_CURRENT, + mBubble.getIntent(), + // Needs to be mutable for the fillInIntent + PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, /* options= */ null); - } else { - fillInIntent = new Intent(pi.getIntent()); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); } mTaskView.startActivity(pi, fillInIntent, options, launchBounds); } else if (isShortcutBubble) { - options.setLaunchedFromBubble(true); - options.setApplyActivityFlagsForBubbles(true); + if (mBubble.isChat()) { + options.setLaunchedFromBubble(true); + options.setApplyActivityFlagsForBubbles(true); + } else { + options.setApplyMultipleTaskFlagForShortcut(true); + } mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); } else { 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 a676f41baafe..338ffe76e6ea 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 @@ -21,6 +21,7 @@ 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 static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE; @@ -325,7 +326,7 @@ public class BubbleTransitions { 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 (chg.getMode() != TRANSIT_CHANGE && chg.getMode() != TRANSIT_TO_FRONT) continue; if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue; mStartBounds.set(chg.getStartAbsBounds()); // Converting a task into taskview, so treat as "new" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 8377a35a9e7d..df82091ef002 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -227,7 +227,13 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged /** Hides the IME for Bubbles when the device is locked. */ public void hideImeForBubblesWhenLocked(int displayId) { PerDisplay pd = mImePerDisplay.get(displayId); - pd.setImeInputTargetRequestedVisibility(false, pd.getImeSourceControl().getImeStatsToken()); + InsetsSourceControl imeSourceControl = pd.getImeSourceControl(); + if (imeSourceControl != null) { + ImeTracker.Token imeStatsToken = imeSourceControl.getImeStatsToken(); + if (imeStatsToken != null) { + pd.setImeInputTargetRequestedVisibility(false, imeStatsToken); + } + } } /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */ @@ -324,8 +330,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } applyVisibilityToLeash(imeSourceControl); } - if (!mImeShowing) { - removeImeSurface(mDisplayId); + if (!android.view.inputmethod.Flags.refactorInsetsController()) { + if (!mImeShowing) { + removeImeSurface(mDisplayId); + } } } } else { @@ -663,7 +671,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_ANIMATION_RUNNING); t.hide(animatingLeash); - removeImeSurface(mDisplayId); + if (!android.view.inputmethod.Flags.refactorInsetsController()) { + removeImeSurface(mDisplayId); + } if (android.view.inputmethod.Flags.refactorInsetsController()) { setVisibleDirectly(false /* visible */, statsToken); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt new file mode 100644 index 000000000000..7a5bc1383ccf --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt @@ -0,0 +1,124 @@ +/* + * 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.wm.shell.common + +import android.app.ActivityManager.RunningTaskInfo +import android.graphics.RectF +import android.view.SurfaceControl +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.shared.annotations.ShellDesktopThread + +/** + * Controller to manage the indicators that show users the current position of the dragged window on + * the new display when performing drag move across displays. + */ +class MultiDisplayDragMoveIndicatorController( + private val displayController: DisplayController, + private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + private val indicatorSurfaceFactory: MultiDisplayDragMoveIndicatorSurface.Factory, + @ShellDesktopThread private val desktopExecutor: ShellExecutor, +) { + @ShellDesktopThread + private val dragIndicators = + mutableMapOf<Int, MutableMap<Int, MultiDisplayDragMoveIndicatorSurface>>() + + /** + * Called during drag move, which started at [startDisplayId]. Updates the position and + * visibility of the drag move indicators for the [taskInfo] based on [boundsDp] on the + * destination displays ([displayIds]) as the dragged window moves. [transactionSupplier] + * provides a [SurfaceControl.Transaction] for applying changes to the indicator surfaces. + * + * It is executed on the [desktopExecutor] to prevent blocking the main thread and avoid jank, + * as creating and manipulating surfaces can be expensive. + */ + fun onDragMove( + boundsDp: RectF, + startDisplayId: Int, + taskInfo: RunningTaskInfo, + displayIds: Set<Int>, + transactionSupplier: () -> SurfaceControl.Transaction, + ) { + desktopExecutor.execute { + for (displayId in displayIds) { + if (displayId == startDisplayId) { + // No need to render indicators on the original display where the drag started. + continue + } + val displayLayout = displayController.getDisplayLayout(displayId) ?: continue + val shouldBeVisible = + RectF.intersects(RectF(boundsDp), displayLayout.globalBoundsDp()) + if ( + dragIndicators[taskInfo.taskId]?.containsKey(displayId) != true && + !shouldBeVisible + ) { + // Skip this display if: + // - It doesn't have an existing indicator that needs to be updated, AND + // - The latest dragged window bounds don't intersect with this display. + continue + } + + val boundsPx = + MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect( + boundsDp, + displayLayout, + ) + + // Get or create the inner map for the current task. + val dragIndicatorsForTask = + dragIndicators.getOrPut(taskInfo.taskId) { mutableMapOf() } + dragIndicatorsForTask[displayId]?.also { existingIndicator -> + val transaction = transactionSupplier() + existingIndicator.relayout(boundsPx, transaction, shouldBeVisible) + transaction.apply() + } ?: run { + val newIndicator = + indicatorSurfaceFactory.create( + taskInfo, + displayController.getDisplay(displayId), + ) + newIndicator.show( + transactionSupplier(), + taskInfo, + rootTaskDisplayAreaOrganizer, + displayId, + boundsPx, + ) + dragIndicatorsForTask[displayId] = newIndicator + } + } + } + } + + /** + * Called when the drag ends. Disposes of the drag move indicator surfaces associated with the + * given [taskId]. [transactionSupplier] provides a [SurfaceControl.Transaction] for applying + * changes to the indicator surfaces. + * + * It is executed on the [desktopExecutor] to ensure that any pending `onDragMove` operations + * have completed before disposing of the surfaces. + */ + fun onDragEnd(taskId: Int, transactionSupplier: () -> SurfaceControl.Transaction) { + desktopExecutor.execute { + dragIndicators.remove(taskId)?.values?.takeIf { it.isNotEmpty() }?.let { indicators -> + val transaction = transactionSupplier() + indicators.forEach { indicator -> + indicator.disposeSurface(transaction) + } + transaction.apply() + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt new file mode 100644 index 000000000000..d05d3b0903d7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt @@ -0,0 +1,151 @@ +/* + * 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.wm.shell.common + +import android.app.ActivityManager.RunningTaskInfo +import android.content.Context +import android.graphics.Color +import android.graphics.Rect +import android.os.Trace +import android.view.Display +import android.view.SurfaceControl +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.ui.graphics.toArgb +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.windowdecor.common.DecorThemeUtil +import com.android.wm.shell.windowdecor.common.Theme + +/** + * Represents the indicator surface that visualizes the current position of a dragged window during + * a multi-display drag operation. + * + * This class manages the creation, display, and manipulation of the [SurfaceControl]s that act as a + * visual indicator, providing feedback to the user about the dragged window's location. + */ +class MultiDisplayDragMoveIndicatorSurface( + context: Context, + taskInfo: RunningTaskInfo, + display: Display, + surfaceControlBuilderFactory: Factory.SurfaceControlBuilderFactory, +) { + private var isVisible = false + + // A container surface to host the veil background + private var veilSurface: SurfaceControl? = null + + private val decorThemeUtil = DecorThemeUtil(context) + private val lightColors = dynamicLightColorScheme(context) + private val darkColors = dynamicDarkColorScheme(context) + + init { + Trace.beginSection("DragIndicatorSurface#init") + + val displayId = display.displayId + veilSurface = + surfaceControlBuilderFactory + .create("Drag indicator veil of Task=${taskInfo.taskId} Display=$displayId") + .setColorLayer() + .setCallsite("DragIndicatorSurface#init") + .setHidden(true) + .build() + + // TODO: b/383069173 - Add icon for the surface. + + Trace.endSection() + } + + /** + * Disposes the indicator surface using the provided [transaction]. + */ + fun disposeSurface(transaction: SurfaceControl.Transaction) { + veilSurface?.let { veil -> transaction.remove(veil) } + veilSurface = null + } + + /** + * Shows the indicator surface at [bounds] on the specified display ([displayId]), + * visualizing the drag of the [taskInfo]. The indicator surface is shown using [transaction], + * and the [rootTaskDisplayAreaOrganizer] is used to reparent the surfaces. + */ + fun show( + transaction: SurfaceControl.Transaction, + taskInfo: RunningTaskInfo, + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + displayId: Int, + bounds: Rect, + ) { + val backgroundColor = + when (decorThemeUtil.getAppTheme(taskInfo)) { + Theme.LIGHT -> lightColors.surfaceContainer + Theme.DARK -> darkColors.surfaceContainer + } + val veil = veilSurface ?: return + isVisible = true + + rootTaskDisplayAreaOrganizer.reparentToDisplayArea(displayId, veil, transaction) + relayout(bounds, transaction, shouldBeVisible = true) + transaction.show(veil).setColor(veil, Color.valueOf(backgroundColor.toArgb()).components) + transaction.apply() + } + + /** + * Repositions and resizes the indicator surface based on [bounds] using [transaction]. The + * [shouldBeVisible] flag indicates whether the indicator is within the display after relayout. + */ + fun relayout(bounds: Rect, transaction: SurfaceControl.Transaction, shouldBeVisible: Boolean) { + if (!isVisible && !shouldBeVisible) { + // No need to relayout if the surface is already invisible and should not be visible. + return + } + isVisible = shouldBeVisible + val veil = veilSurface ?: return + transaction.setCrop(veil, bounds) + } + + /** + * Factory for creating [MultiDisplayDragMoveIndicatorSurface] instances with the [context]. + */ + class Factory(private val context: Context) { + private val surfaceControlBuilderFactory: SurfaceControlBuilderFactory = + object : SurfaceControlBuilderFactory {} + + /** + * Creates a new [MultiDisplayDragMoveIndicatorSurface] instance to visualize the drag + * operation of the [taskInfo] on the given [display]. + */ + fun create( + taskInfo: RunningTaskInfo, + display: Display, + ) = MultiDisplayDragMoveIndicatorSurface( + context, + taskInfo, + display, + surfaceControlBuilderFactory, + ) + + /** + * Interface for creating [SurfaceControl.Builder] instances. + * + * This provides an abstraction over [SurfaceControl.Builder] creation for testing purposes. + */ + interface SurfaceControlBuilderFactory { + fun create(name: String): SurfaceControl.Builder { + return SurfaceControl.Builder().setName(name) + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java index 8e026f04ac31..04e8d8dee520 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java @@ -24,6 +24,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.graphics.Rect; import android.util.DisplayMetrics; +import android.util.Rational; import android.util.Size; import android.view.Gravity; @@ -41,9 +42,6 @@ public class PipBoundsAlgorithm { private static final String TAG = PipBoundsAlgorithm.class.getSimpleName(); private static final float INVALID_SNAP_FRACTION = -1f; - // The same value (with the same name) is used in Launcher. - private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f; - @NonNull private final PipBoundsState mPipBoundsState; @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState; @NonNull protected final SizeSpecSource mSizeSpecSource; @@ -223,9 +221,8 @@ public class PipBoundsAlgorithm { + " than destination(%s)", sourceRectHint, destinationBounds); return false; } - final float reportedRatio = destinationBounds.width() / (float) destinationBounds.height(); - final float inferredRatio = sourceRectHint.width() / (float) sourceRectHint.height(); - if (Math.abs(reportedRatio - inferredRatio) > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) { + if (!PictureInPictureParams.isSameAspectRatio(sourceRectHint, + new Rational(destinationBounds.width(), destinationBounds.height()))) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "isSourceRectHintValidForEnterPip=false, hint(%s) does not match" + " destination(%s) aspect ratio", sourceRectHint, destinationBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java index ee3e39e71558..e9dc6132f5f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java @@ -162,22 +162,31 @@ public abstract class WMShellConcurrencyModule { } } - /** - * Provide a Shell animation-thread Executor. - */ + /** Provide a Shell animation-thread Handler. */ @WMSingleton @Provides @ShellAnimationThread - public static ShellExecutor provideShellAnimationExecutor() { - HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim", - THREAD_PRIORITY_DISPLAY); - shellAnimationThread.start(); + public static Handler provideShellAnimationHandler() { + HandlerThread animThread = new HandlerThread("wmshell.anim", THREAD_PRIORITY_DISPLAY); + animThread.start(); if (Build.IS_DEBUGGABLE) { - shellAnimationThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); - shellAnimationThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, + animThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); + animThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, MSGQ_SLOW_DELIVERY_THRESHOLD_MS); } - return new HandlerExecutor(Handler.createAsync(shellAnimationThread.getLooper())); + return Handler.createAsync(animThread.getLooper()); + } + + /** + * Provide a Shell animation-thread Executor. + */ + @WMSingleton + @Provides + @ShellAnimationThread + public static ShellExecutor provideShellAnimationExecutor( + @ShellAnimationThread Handler animHandler + ) { + return new HandlerExecutor(animHandler); } /** 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 2fd8c27d5970..5d5e4d3ec758 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 @@ -68,6 +68,8 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.LaunchAdjacentController; +import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController; +import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorSurface; import com.android.wm.shell.common.MultiInstanceHelper; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; @@ -82,6 +84,7 @@ import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler; import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler; +import com.android.wm.shell.desktopmode.DesktopDisplayModeController; import com.android.wm.shell.desktopmode.DesktopImmersiveController; import com.android.wm.shell.desktopmode.DesktopMinimizationTransitionHandler; import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler; @@ -428,9 +431,10 @@ public abstract class WMShellModule { Transitions transitions, DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor, - @ShellAnimationThread ShellExecutor animExecutor) { + @ShellAnimationThread ShellExecutor animExecutor, + @ShellAnimationThread Handler animHandler) { return new FreeformTaskTransitionHandler( - transitions, displayController, mainExecutor, animExecutor); + transitions, displayController, mainExecutor, animExecutor, animHandler); } @WMSingleton @@ -989,7 +993,8 @@ public abstract class WMShellModule { WindowDecorTaskResourceLoader taskResourceLoader, RecentsTransitionHandler recentsTransitionHandler, DesktopModeCompatPolicy desktopModeCompatPolicy, - DesktopTilingDecorViewModel desktopTilingDecorViewModel + DesktopTilingDecorViewModel desktopTilingDecorViewModel, + MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController ) { if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) { return Optional.empty(); @@ -1006,7 +1011,30 @@ public abstract class WMShellModule { windowDecorCaptionHandleRepository, activityOrientationChangeHandler, focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger, taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy, - desktopTilingDecorViewModel)); + desktopTilingDecorViewModel, + multiDisplayDragMoveIndicatorController)); + } + + @WMSingleton + @Provides + static MultiDisplayDragMoveIndicatorController + providesMultiDisplayDragMoveIndicatorController( + DisplayController displayController, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + MultiDisplayDragMoveIndicatorSurface.Factory + multiDisplayDragMoveIndicatorSurfaceFactory, + @ShellDesktopThread ShellExecutor desktopExecutor + ) { + return new MultiDisplayDragMoveIndicatorController( + displayController, rootTaskDisplayAreaOrganizer, + multiDisplayDragMoveIndicatorSurfaceFactory, desktopExecutor); + } + + @WMSingleton + @Provides + static MultiDisplayDragMoveIndicatorSurface.Factory + providesMultiDisplayDragMoveIndicatorSurfaceFactory(Context context) { + return new MultiDisplayDragMoveIndicatorSurface.Factory(context); } @WMSingleton @@ -1074,8 +1102,9 @@ public abstract class WMShellModule { Context context, @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor, - @ShellMainThread Handler handler) { - return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor, handler); + @ShellAnimationThread Handler animHandler) { + return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor, + animHandler); } @WMSingleton @@ -1083,9 +1112,10 @@ public abstract class WMShellModule { static DesktopMinimizationTransitionHandler provideDesktopMinimizationTransitionHandler( @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor, - DisplayController displayController) { + DisplayController displayController, + @ShellAnimationThread Handler mainHandler) { return new DesktopMinimizationTransitionHandler(mainExecutor, animExecutor, - displayController); + displayController, mainHandler); } @WMSingleton @@ -1230,13 +1260,10 @@ public abstract class WMShellModule { static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler( Context context, ShellInit shellInit, - Transitions transitions, DisplayController displayController, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - IWindowManager windowManager, Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, - ShellTaskOrganizer shellTaskOrganizer + Optional<DesktopDisplayModeController> desktopDisplayModeController ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.empty(); @@ -1245,13 +1272,10 @@ public abstract class WMShellModule { new DesktopDisplayEventHandler( context, shellInit, - transitions, displayController, - rootTaskDisplayAreaOrganizer, - windowManager, desktopUserRepositories.get(), desktopTasksController.get(), - shellTaskOrganizer)); + desktopDisplayModeController.get())); } @WMSingleton @@ -1381,6 +1405,29 @@ public abstract class WMShellModule { return new DesktopModeUiEventLogger(uiEventLogger, packageManager); } + @WMSingleton + @Provides + static Optional<DesktopDisplayModeController> provideDesktopDisplayModeController( + Context context, + Transitions transitions, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + IWindowManager windowManager, + ShellTaskOrganizer shellTaskOrganizer, + DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider + ) { + if (!DesktopModeStatus.canEnterDesktopMode(context)) { + return Optional.empty(); + } + return Optional.of( + new DesktopDisplayModeController( + context, + transitions, + rootTaskDisplayAreaOrganizer, + windowManager, + shellTaskOrganizer, + desktopWallpaperActivityTokenProvider)); + } + // // App zoom out // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt index 1ce093e02a4a..b22a46edbf3a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt @@ -37,7 +37,6 @@ import com.android.app.animation.Interpolators import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_CLOSE_TASK import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions import java.util.function.Supplier @@ -49,7 +48,7 @@ constructor( private val mainExecutor: ShellExecutor, private val animExecutor: ShellExecutor, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, - @ShellMainThread private val handler: Handler, + private val animHandler: Handler, ) : Transitions.TransitionHandler { private val runningAnimations = mutableMapOf<IBinder, List<Animator>>() @@ -95,7 +94,7 @@ constructor( interactionJankMonitor.begin( lastChangeLeash, context, - handler, + animHandler, CUJ_DESKTOP_MODE_CLOSE_TASK, ) } 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 c38558d7bde9..946e7952dd50 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,41 +16,25 @@ 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 import android.view.Display.DEFAULT_DISPLAY -import android.view.IWindowManager -import android.view.WindowManager.TRANSIT_CHANGE import android.window.DesktopExperienceFlags -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 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit -import com.android.wm.shell.transition.Transitions /** Handles display events in desktop mode */ class DesktopDisplayEventHandler( private val context: Context, shellInit: ShellInit, - private val transitions: Transitions, private val displayController: DisplayController, - private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, - private val windowManager: IWindowManager, private val desktopUserRepositories: DesktopUserRepositories, private val desktopTasksController: DesktopTasksController, - private val shellTaskOrganizer: ShellTaskOrganizer, + private val desktopDisplayModeController: DesktopDisplayModeController, ) : OnDisplaysChangedListener, OnDeskRemovedListener { private val desktopRepository: DesktopRepository @@ -70,7 +54,7 @@ class DesktopDisplayEventHandler( override fun onDisplayAdded(displayId: Int) { if (displayId != DEFAULT_DISPLAY) { - refreshDisplayWindowingMode() + desktopDisplayModeController.refreshDisplayWindowingMode() } if (!supportsDesks(displayId)) { @@ -88,7 +72,7 @@ class DesktopDisplayEventHandler( override fun onDisplayRemoved(displayId: Int) { if (displayId != DEFAULT_DISPLAY) { - refreshDisplayWindowingMode() + desktopDisplayModeController.refreshDisplayWindowingMode() } // TODO: b/362720497 - move desks in closing display to the remaining desk. @@ -102,65 +86,6 @@ class DesktopDisplayEventHandler( } } - private fun refreshDisplayWindowingMode() { - if (!Flags.enableDisplayWindowingModeSwitching()) return - // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available. - val isExtendedDisplayEnabled = - 0 != - Settings.Global.getInt( - context.contentResolver, - DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, - 0, - ) - if (!isExtendedDisplayEnabled) { - // No action needed in mirror or projected mode. - return - } - - val hasNonDefaultDisplay = - rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId -> - displayId != DEFAULT_DISPLAY - } - val targetDisplayWindowingMode = - if (hasNonDefaultDisplay) { - WINDOWING_MODE_FREEFORM - } else { - // Use the default display windowing mode when no non-default display. - windowManager.getWindowingMode(DEFAULT_DISPLAY) - } - val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY) - requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." } - 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) - } - // TODO: b/362720497 - connected/projected display considerations. private fun supportsDesks(displayId: Int): Boolean = DesktopModeStatus.canEnterDesktopMode(context) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt new file mode 100644 index 000000000000..c9a63ff818f5 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt @@ -0,0 +1,121 @@ +/* + * 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.wm.shell.desktopmode + +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +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 +import android.view.Display.DEFAULT_DISPLAY +import android.view.IWindowManager +import android.view.WindowManager.TRANSIT_CHANGE +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.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.transition.Transitions + +/** Controls the display windowing mode in desktop mode */ +class DesktopDisplayModeController( + private val context: Context, + private val transitions: Transitions, + private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + private val windowManager: IWindowManager, + private val shellTaskOrganizer: ShellTaskOrganizer, + private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, +) { + + fun refreshDisplayWindowingMode() { + if (!Flags.enableDisplayWindowingModeSwitching()) return + // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available. + val isExtendedDisplayEnabled = + 0 != + Settings.Global.getInt( + context.contentResolver, + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, + 0, + ) + if (!isExtendedDisplayEnabled) { + // No action needed in mirror or projected mode. + return + } + + val hasNonDefaultDisplay = + rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId -> + displayId != DEFAULT_DISPLAY + } + val targetDisplayWindowingMode = + if (hasNonDefaultDisplay) { + WINDOWING_MODE_FREEFORM + } else { + // Use the default display windowing mode when no non-default display. + windowManager.getWindowingMode(DEFAULT_DISPLAY) + } + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY) + requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." } + 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) + } + } + } + // The override windowing mode of DesktopWallpaper can be UNDEFINED on fullscreen-display + // right after the first launch while its resolved windowing mode is FULLSCREEN. We here + // it has the FULLSCREEN override windowing mode. + desktopWallpaperActivityTokenProvider.getToken(DEFAULT_DISPLAY)?.let { token -> + wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN) + } + transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) + } + + private fun logV(msg: String, vararg arguments: Any?) { + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + companion object { + private const val TAG = "DesktopDisplayModeController" + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt index 728638d9bbd3..7074e8bc9cce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt @@ -18,14 +18,17 @@ package com.android.wm.shell.desktopmode import android.animation.Animator import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.os.Handler import android.os.IBinder -import android.util.DisplayMetrics import android.view.SurfaceControl.Transaction import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.animation.MinimizeAnimator.create import com.android.wm.shell.transition.Transitions @@ -41,6 +44,7 @@ class DesktopMinimizationTransitionHandler( private val mainExecutor: ShellExecutor, private val animExecutor: ShellExecutor, private val displayController: DisplayController, + private val animHandler: Handler, ) : Transitions.TransitionHandler { /** Shouldn't handle anything */ @@ -90,10 +94,30 @@ class DesktopMinimizationTransitionHandler( val t = Transaction() val sc = change.leash finishTransaction.hide(sc) - val displayMetrics: DisplayMetrics? = - change.taskInfo?.let { - displayController.getDisplayContext(it.displayId)?.getResources()?.displayMetrics - } - return displayMetrics?.let { create(it, change, t, onAnimFinish) } + val displayContext = + change.taskInfo?.let { displayController.getDisplayContext(it.displayId) } + if (displayContext == null) { + logW( + "displayContext is null for taskId=${change.taskInfo?.taskId}, " + + "displayId=${change.taskInfo?.displayId}" + ) + return null + } + return create( + displayContext, + change, + t, + onAnimFinish, + InteractionJankMonitor.getInstance(), + animHandler, + ) + } + + private companion object { + private fun logW(msg: String, vararg arguments: Any?) { + ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + const val TAG = "DesktopMinimizationTransitionHandler" } } 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 031925b2997a..7c6cf4a8b37f 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 @@ -332,29 +332,22 @@ class DesktopRepository( return false } - /** - * Adds given task to the closing task list for [displayId]'s active desk. - * - * TODO: b/389960283 - add explicit [deskId] argument. - */ - fun addClosingTask(displayId: Int, taskId: Int) { - val activeDesk = - desktopData.getActiveDesk(displayId) - ?: error("Expected active desk in display: $displayId") - if (activeDesk.closingTasks.add(taskId)) { - logD( - "Added closing task=%d displayId=%d deskId=%d", - taskId, - displayId, - activeDesk.deskId, - ) + /** Adds given task to the closing task list of its desk. */ + fun addClosingTask(displayId: Int, deskId: Int?, taskId: Int) { + val desk = + deskId?.let { desktopData.getDesk(it) } + ?: checkNotNull(desktopData.getActiveDesk(displayId)) { + "Expected active desk in display: $displayId" + } + if (desk.closingTasks.add(taskId)) { + logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, desk.deskId) } else { // If the task hasn't been removed from closing list after it disappeared. logW( "Task with taskId=%d displayId=%d deskId=%d is already closing", taskId, displayId, - activeDesk.deskId, + desk.deskId, ) } } @@ -392,7 +385,8 @@ class DesktopRepository( * Checks if a task is the only visible, non-closing, non-minimized task on the active desk of * the given display, or any display's active desk if [displayId] is [INVALID_DISPLAY]. * - * TODO: b/389960283 - add explicit [deskId] argument. + * TODO: b/389960283 - consider forcing callers to use [isOnlyVisibleNonClosingTaskInDesk] with + * an explicit desk id instead of using this function and defaulting to the active one. */ fun isOnlyVisibleNonClosingTask(taskId: Int, displayId: Int = INVALID_DISPLAY): Boolean { val activeDesks = @@ -402,14 +396,27 @@ class DesktopRepository( desktopData.getAllActiveDesks() } return activeDesks.any { desk -> - desk.visibleTasks - .subtract(desk.closingTasks) - .subtract(desk.minimizedTasks) - .singleOrNull() == taskId + isOnlyVisibleNonClosingTaskInDesk( + taskId = taskId, + deskId = desk.deskId, + displayId = desk.displayId, + ) } } /** + * Checks if a task is the only visible, non-closing, non-minimized task on the given desk of + * the given display. + */ + fun isOnlyVisibleNonClosingTaskInDesk(taskId: Int, deskId: Int, displayId: Int): Boolean { + val desk = desktopData.getDesk(deskId) ?: return false + return desk.visibleTasks + .subtract(desk.closingTasks) + .subtract(desk.minimizedTasks) + .singleOrNull() == taskId + } + + /** * Returns the active tasks in the given display's active desk. * * TODO: b/389960283 - migrate callers to [getActiveTaskIdsInDesk]. @@ -686,6 +693,11 @@ class DesktopRepository( * TODO: b/389960283 - add explicit [deskId] argument. */ fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) { + logD( + "Top transparent fullscreen task set for display: taskId=%d, displayId=%d", + taskId, + displayId, + ) desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = taskId } @@ -703,6 +715,11 @@ class DesktopRepository( * TODO: b/389960283 - add explicit [deskId] argument. */ fun clearTopTransparentFullscreenTaskId(displayId: Int) { + logD( + "Top transparent fullscreen task cleared for display: taskId=%d, displayId=%d", + desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId, + displayId, + ) desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null } 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 93058db0c171..ca870d2b6988 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 @@ -117,6 +117,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING +import com.android.wm.shell.shared.R as SharedR import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellDesktopThread @@ -801,6 +802,9 @@ class DesktopTasksController( ): ((IBinder) -> Unit) { val taskId = taskInfo.taskId val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) + if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + error("Did not find desk for task: $taskId") + } snapEventHandler.removeTaskIfTiled(displayId, taskId) val shouldExitDesktop = willExitDesktop( @@ -818,7 +822,7 @@ class DesktopTasksController( shouldEndUpAtHome = true, ) - taskRepository.addClosingTask(displayId, taskId) + taskRepository.addClosingTask(displayId = displayId, deskId = deskId, taskId = taskId) taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( doesAnyTaskRequireTaskbarRounding(displayId, taskId) ) @@ -870,6 +874,10 @@ class DesktopTasksController( private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val taskId = taskInfo.taskId val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) + if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + logW("minimizeTaskInner: desk not found for task: ${taskInfo.taskId}") + return + } val displayId = taskInfo.displayId val wct = WindowContainerTransaction() @@ -890,10 +898,26 @@ class DesktopTasksController( taskInfo = taskInfo, reason = DesktopImmersiveController.ExitReason.MINIMIZED, ) - - wct.reorder(taskInfo.token, false) - val isLastTask = taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId) - val transition: IBinder = + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + desksOrganizer.minimizeTask( + wct = wct, + deskId = checkNotNull(deskId) { "Expected non-null deskId" }, + task = taskInfo, + ) + } else { + wct.reorder(taskInfo.token, /* onTop= */ false) + } + val isLastTask = + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + taskRepository.isOnlyVisibleNonClosingTaskInDesk( + taskId = taskId, + deskId = checkNotNull(deskId) { "Expected non-null deskId" }, + displayId = displayId, + ) + } else { + taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId) + } + val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask) desktopTasksLimiter.ifPresent { it.addPendingMinimizeChange( @@ -1055,7 +1079,8 @@ class DesktopTasksController( ) } - private fun startLaunchTransition( + @VisibleForTesting + fun startLaunchTransition( transitionType: Int, wct: WindowContainerTransaction, launchingTaskId: Int?, @@ -1063,34 +1088,52 @@ class DesktopTasksController( displayId: Int = DEFAULT_DISPLAY, unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, ): IBinder { + // TODO: b/397619806 - Consolidate sharable logic with [handleFreeformTaskLaunch]. + var launchTransaction = wct val taskIdToMinimize = addAndGetMinimizeChanges( displayId, - wct, + launchTransaction, newTaskId = launchingTaskId, launchingNewIntent = launchingTaskId == null, ) val exitImmersiveResult = desktopImmersiveController.exitImmersiveIfApplicable( - wct = wct, + wct = launchTransaction, displayId = displayId, excludeTaskId = launchingTaskId, reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) + var deskIdToActivate: Int? = null + if ( + DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue && + !isDesktopModeShowing(displayId) + ) { + deskIdToActivate = + checkNotNull( + launchingTaskId?.let { taskRepository.getDeskIdForTask(it) } + ?: getDefaultDeskId(displayId) + ) + val activateDeskWct = WindowContainerTransaction() + addDeskActivationChanges(deskIdToActivate, activateDeskWct) + // Desk activation must be handled before app launch-related transactions. + activateDeskWct.merge(launchTransaction, /* transfer= */ true) + launchTransaction = activateDeskWct + } val t = if (remoteTransition == null) { desktopMixedTransitionHandler.startLaunchTransition( transitionType = transitionType, - wct = wct, + wct = launchTransaction, taskId = launchingTaskId, minimizingTaskId = taskIdToMinimize, exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask, ) } else if (taskIdToMinimize == null) { val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition) - transitions.startTransition(transitionType, wct, remoteTransitionHandler).also { - remoteTransitionHandler.setTransition(it) - } + transitions + .startTransition(transitionType, launchTransaction, remoteTransitionHandler) + .also { remoteTransitionHandler.setTransition(it) } } else { val remoteTransitionHandler = DesktopWindowLimitRemoteHandler( @@ -1099,9 +1142,9 @@ class DesktopTasksController( remoteTransition, taskIdToMinimize, ) - transitions.startTransition(transitionType, wct, remoteTransitionHandler).also { - remoteTransitionHandler.setTransition(it) - } + transitions + .startTransition(transitionType, launchTransaction, remoteTransitionHandler) + .also { remoteTransitionHandler.setTransition(it) } } if (taskIdToMinimize != null) { addPendingMinimizeTransition(t, taskIdToMinimize, MinimizeReason.TASK_LIMIT) @@ -1109,6 +1152,24 @@ class DesktopTasksController( if (launchingTaskId != null && taskRepository.isMinimizedTask(launchingTaskId)) { addPendingUnminimizeTransition(t, displayId, launchingTaskId, unminimizeReason) } + if ( + DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue && + deskIdToActivate != null + ) { + if (DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) { + desksTransitionObserver.addPendingTransition( + DeskTransition.ActivateDesk( + token = t, + displayId = displayId, + deskId = deskIdToActivate, + ) + ) + } + + desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( + FREEFORM_ANIMATION_DURATION + ) + } exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t) return t } @@ -1231,9 +1292,9 @@ class DesktopTasksController( // home. if (Flags.enablePerDisplayDesktopWallpaperActivity()) { performDesktopExitCleanupIfNeeded( - task.taskId, - task.displayId, - wct, + taskId = task.taskId, + displayId = task.displayId, + wct = wct, forceToFullscreen = false, // TODO: b/371096166 - Temporary turing home relaunch off to prevent home stealing // display focus. Remove shouldEndUpAtHome = false when home focus handling @@ -1800,6 +1861,7 @@ class DesktopTasksController( private fun performDesktopExitCleanupIfNeeded( taskId: Int, + deskId: Int? = null, displayId: Int, wct: WindowContainerTransaction, forceToFullscreen: Boolean, @@ -1813,13 +1875,14 @@ class DesktopTasksController( // |RunOnTransitStart| when the transition is started. return performDesktopExitCleanUp( wct = wct, - deskId = null, + deskId = deskId, displayId = displayId, willExitDesktop = true, shouldEndUpAtHome = shouldEndUpAtHome, ) } + /** TODO: b/394268248 - update [deskId] to be non-null. */ private fun performDesktopExitCleanUp( wct: WindowContainerTransaction, deskId: Int?, @@ -2015,7 +2078,9 @@ class DesktopTasksController( } val cornerRadius = context.resources - .getDimensionPixelSize(R.dimen.desktop_windowing_freeform_rounded_corner_radius) + .getDimensionPixelSize( + SharedR.dimen.desktop_windowing_freeform_rounded_corner_radius + ) .toFloat() info.changes .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM } @@ -2370,17 +2435,28 @@ class DesktopTasksController( ): WindowContainerTransaction? { logV("handleTaskClosing") if (!isDesktopModeShowing(task.displayId)) return null + val deskId = taskRepository.getDeskIdForTask(task.taskId) + if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + return null + } val wct = WindowContainerTransaction() - performDesktopExitCleanupIfNeeded( - task.taskId, - task.displayId, - wct, - forceToFullscreen = false, - ) + val deactivationRunnable = + performDesktopExitCleanupIfNeeded( + taskId = task.taskId, + deskId = deskId, + displayId = task.displayId, + wct = wct, + forceToFullscreen = false, + ) + deactivationRunnable?.invoke(transition) if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { - taskRepository.addClosingTask(task.displayId, task.taskId) + taskRepository.addClosingTask( + displayId = task.displayId, + deskId = deskId, + taskId = task.taskId, + ) snapEventHandler.removeTaskIfTiled(task.displayId, task.taskId) } @@ -2584,9 +2660,9 @@ class DesktopTasksController( wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) performDesktopExitCleanupIfNeeded( - taskInfo.taskId, - taskInfo.displayId, - wct, + taskId = taskInfo.taskId, + displayId = taskInfo.displayId, + wct = wct, forceToFullscreen = false, shouldEndUpAtHome = false, ) @@ -2665,10 +2741,9 @@ class DesktopTasksController( activateDesk(deskId, remoteTransition) } - /** Activates the given desk. */ - fun activateDesk(deskId: Int, remoteTransition: RemoteTransition? = null) { + /** Activates the given desk but without starting a transition. */ + fun addDeskActivationChanges(deskId: Int, wct: WindowContainerTransaction) { val displayId = taskRepository.getDisplayForDesk(deskId) - val wct = WindowContainerTransaction() if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { prepareForDeskActivation(displayId, wct) desksOrganizer.activateDesk(wct, deskId) @@ -2681,6 +2756,13 @@ class DesktopTasksController( } else { bringDesktopAppsToFront(displayId, wct) } + } + + /** Activates the given desk. */ + fun activateDesk(deskId: Int, remoteTransition: RemoteTransition? = null) { + val displayId = taskRepository.getDisplayForDesk(deskId) + val wct = WindowContainerTransaction() + addDeskActivationChanges(deskId, wct) val transitionType = transitionType(remoteTransition) val handler = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 81b136dd1569..f9ab359e952d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -26,7 +26,6 @@ import android.window.DesktopModeFlags import android.window.TransitionInfo import android.window.WindowContainerTransaction import androidx.annotation.VisibleForTesting -import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer @@ -176,28 +175,13 @@ class DesktopTasksLimiter( return taskChange.mode == TRANSIT_TO_BACK } - override fun onTransitionStarting(transition: IBinder) { - val mActiveTaskDetails = activeTransitionTokensAndTasks[transition] - val info = mActiveTaskDetails?.transitionInfo ?: return - val minimizeChange = getMinimizeChange(info, mActiveTaskDetails.taskId) ?: return - // Begin minimize window CUJ instrumentation. - interactionJankMonitor.begin( - minimizeChange.leash, - context, - handler, - CUJ_DESKTOP_MODE_MINIMIZE_WINDOW, - ) - } - private fun getMinimizeChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? = info.changes.find { change -> change.taskInfo?.taskId == taskId && change.mode == TRANSIT_TO_BACK } override fun onTransitionMerged(merged: IBinder, playing: IBinder) { - if (activeTransitionTokensAndTasks.remove(merged) != null) { - interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) - } + activeTransitionTokensAndTasks.remove(merged) pendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> pendingTransitionTokensAndTasks[playing] = taskToTransfer } @@ -209,13 +193,6 @@ class DesktopTasksLimiter( } override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { - if (activeTransitionTokensAndTasks.remove(transition) != null) { - if (aborted) { - interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) - } else { - interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) - } - } pendingTransitionTokensAndTasks.remove(transition) activeUnminimizeTransitionTokensAndTasks.remove(transition) pendingUnminimizeTransitionTokensAndTasks.remove(transition) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl index 59add47fc79d..5f45192569e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl @@ -18,13 +18,11 @@ package com.android.wm.shell.desktopmode; /** * Defines the state of desks on a display whose ID is `displayId`, which is: - * - `canCreateDesks`: whether it's possible to create new desks on this display. * - `activeDeskId`: the currently active desk Id, or `-1` if none is active. * - `deskId`: the list of desk Ids of the available desks on this display. */ parcelable DisplayDeskState { int displayId; - boolean canCreateDesk; int activeDeskId; int[] deskIds; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl index 7ed1581cdfdb..cefbd8947feb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl @@ -28,7 +28,7 @@ oneway interface IDesktopTaskListener { * Called once when the listener first gets connected to initialize it with the current state of * desks in Shell. */ - void onListenerConnected(in DisplayDeskState[] displayDeskStates); + void onListenerConnected(in DisplayDeskState[] displayDeskStates, boolean canCreateDesks); /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */ void onTasksVisibilityChanged(int displayId, int visibleTasksCount); @@ -49,10 +49,10 @@ oneway interface IDesktopTaskListener { void onExitDesktopModeTransitionStarted(int transitionDuration); /** - * Called when the conditions that allow the creation of a new desk on the display whose ID is - * `displayId` changes to `canCreateDesks`. It's also called when a new display is added. + * Called when the conditions that allow the creation of a new desk changes. This is a global + * state for the entire device. */ - void onCanCreateDesksChanged(int displayId, boolean canCreateDesks); + void onCanCreateDesksChanged(boolean canCreateDesks); /** Called when a desk whose ID is `deskId` is added to the display whose ID is `displayId`. */ void onDeskAdded(int displayId, int deskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt index 0f2f3711a9a3..fc359d7d67b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt @@ -40,9 +40,19 @@ interface DesksOrganizer { task: ActivityManager.RunningTaskInfo, ) + /** Minimizes the given task of the given deskId. */ + fun minimizeTask( + wct: WindowContainerTransaction, + deskId: Int, + task: ActivityManager.RunningTaskInfo, + ) + /** Whether the change is for the given desk id. */ fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean + /** Whether the change is for a known desk. */ + fun isDeskChange(change: TransitionInfo.Change): Boolean + /** * Returns the desk id in which the task in the given change is located at the end of a * transition, if any. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt index 339932cabd2c..f576258ebdaa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt @@ -15,7 +15,9 @@ */ package com.android.wm.shell.desktopmode.multidesks +import android.annotation.SuppressLint import android.app.ActivityManager.RunningTaskInfo +import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -25,6 +27,7 @@ import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.DesktopExperienceFlags import android.window.TransitionInfo +import android.window.WindowContainerToken import android.window.WindowContainerTransaction import androidx.core.util.forEach import com.android.internal.annotations.VisibleForTesting @@ -43,8 +46,12 @@ class RootTaskDesksOrganizer( private val shellTaskOrganizer: ShellTaskOrganizer, ) : DesksOrganizer, ShellTaskOrganizer.TaskListener { - private val deskCreateRequests = mutableListOf<CreateRequest>() - @VisibleForTesting val roots = SparseArray<DeskRoot>() + private val createDeskRootRequests = mutableListOf<CreateDeskRequest>() + @VisibleForTesting val deskRootsByDeskId = SparseArray<DeskRoot>() + private val createDeskMinimizationRootRequests = + mutableListOf<CreateDeskMinimizationRootRequest>() + @VisibleForTesting + val deskMinimizationRootsByDeskId: MutableMap<Int, DeskMinimizationRoot> = mutableMapOf() init { if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { @@ -57,7 +64,7 @@ class RootTaskDesksOrganizer( override fun createDesk(displayId: Int, callback: OnCreateCallback) { logV("createDesk in display: %d", displayId) - deskCreateRequests += CreateRequest(displayId, callback) + createDeskRootRequests += CreateDeskRequest(displayId, callback) shellTaskOrganizer.createRootTask( displayId, WINDOWING_MODE_FREEFORM, @@ -68,14 +75,14 @@ class RootTaskDesksOrganizer( override fun removeDesk(wct: WindowContainerTransaction, deskId: Int) { logV("removeDesk %d", deskId) - val desk = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" } - wct.removeRootTask(desk.taskInfo.token) + deskRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) } + deskMinimizationRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) } } override fun activateDesk(wct: WindowContainerTransaction, deskId: Int) { logV("activateDesk %d", deskId) - val root = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" } - wct.reorder(root.taskInfo.token, /* onTop= */ true) + val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } + wct.reorder(root.token, /* onTop= */ true) wct.setLaunchRoot( /* container= */ root.taskInfo.token, /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED), @@ -85,7 +92,7 @@ class RootTaskDesksOrganizer( override fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int) { logV("deactivateDesk %d", deskId) - val root = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" } + val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } wct.setLaunchRoot( /* container= */ root.taskInfo.token, /* windowingModes= */ null, @@ -98,16 +105,58 @@ class RootTaskDesksOrganizer( deskId: Int, task: RunningTaskInfo, ) { - val root = roots[deskId] ?: error("Root not found for desk: $deskId") + val root = deskRootsByDeskId[deskId] ?: error("Root not found for desk: $deskId") wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true) } + override fun minimizeTask(wct: WindowContainerTransaction, deskId: Int, task: RunningTaskInfo) { + val deskRoot = + checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } + val minimizationRoot = + checkNotNull(deskMinimizationRootsByDeskId[deskId]) { + "Minimization root not found for desk: $deskId" + } + val taskId = task.taskId + if (taskId in minimizationRoot.children) { + logV("Task #$taskId is already minimized in desk #$deskId") + return + } + if (taskId !in deskRoot.children) { + logE("Attempted to minimize task=${task.taskId} in desk=$deskId but it was not a child") + return + } + wct.reparent(task.token, minimizationRoot.token, /* onTop= */ true) + } + override fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean = - roots.contains(deskId) && change.taskInfo?.taskId == deskId + (isDeskRootChange(change) && change.taskId == deskId) || + (getDeskMinimizationRootInChange(change)?.deskId == deskId) + + override fun isDeskChange(change: TransitionInfo.Change): Boolean = + isDeskRootChange(change) || getDeskMinimizationRootInChange(change) != null + + private fun isDeskRootChange(change: TransitionInfo.Change): Boolean = + change.taskId in deskRootsByDeskId - override fun getDeskAtEnd(change: TransitionInfo.Change): Int? = - change.taskInfo?.parentTaskId?.takeIf { it in roots } + private fun getDeskMinimizationRootInChange( + change: TransitionInfo.Change + ): DeskMinimizationRoot? = + deskMinimizationRootsByDeskId.values.find { it.rootId == change.taskId } + + private val TransitionInfo.Change.taskId: Int + get() = taskInfo?.taskId ?: INVALID_TASK_ID + + override fun getDeskAtEnd(change: TransitionInfo.Change): Int? { + val parentTaskId = change.taskInfo?.parentTaskId ?: return null + if (parentTaskId in deskRootsByDeskId) { + return parentTaskId + } + val deskMinimizationRoot = + deskMinimizationRootsByDeskId.values.find { root -> root.rootId == parentTaskId } + ?: return null + return deskMinimizationRoot.deskId + } override fun isDeskActiveAtEnd(change: TransitionInfo.Change, deskId: Int): Boolean = change.taskInfo?.taskId == deskId && @@ -115,51 +164,176 @@ class RootTaskDesksOrganizer( change.mode == TRANSIT_TO_FRONT override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) { - if (taskInfo.parentTaskId in roots) { + // Check whether this task is appearing inside a desk. + if (taskInfo.parentTaskId in deskRootsByDeskId) { val deskId = taskInfo.parentTaskId val taskId = taskInfo.taskId logV("Task #$taskId appeared in desk #$deskId") addChildToDesk(taskId = taskId, deskId = deskId) return } - val deskId = taskInfo.taskId - check(deskId !in roots) { "A root already exists for desk: $deskId" } - val request = - checkNotNull(deskCreateRequests.firstOrNull { it.displayId == taskInfo.displayId }) { - "Task ${taskInfo.taskId} appeared without pending create request" - } - logV("Desk #$deskId appeared") - roots[deskId] = DeskRoot(deskId, taskInfo, leash) - deskCreateRequests.remove(request) - request.onCreateCallback.onCreated(deskId) + // Check whether this task is appearing in a minimization root. + val minimizationRoot = + deskMinimizationRootsByDeskId.values.singleOrNull { it.rootId == taskInfo.parentTaskId } + if (minimizationRoot != null) { + val deskId = minimizationRoot.deskId + val taskId = taskInfo.taskId + logV("Task #$taskId was minimized in desk #$deskId ") + addChildToMinimizationRoot(taskId = taskId, deskId = deskId) + return + } + // The appearing task is a root (either a desk or a minimization root), it should not exist + // already. + check(taskInfo.taskId !in deskRootsByDeskId) { + "A root already exists for desk: ${taskInfo.taskId}" + } + check(deskMinimizationRootsByDeskId.values.none { it.rootId == taskInfo.taskId }) { + "A minimization root already exists with rootId: ${taskInfo.taskId}" + } + + val appearingInDisplayId = taskInfo.displayId + // Check if there's any pending desk creation requests under this display. + val deskRequest = + createDeskRootRequests.firstOrNull { it.displayId == appearingInDisplayId } + if (deskRequest != null) { + // Appearing root matches desk request. + val deskId = taskInfo.taskId + logV("Desk #$deskId appeared") + deskRootsByDeskId[deskId] = DeskRoot(deskId, taskInfo, leash) + createDeskRootRequests.remove(deskRequest) + deskRequest.onCreateCallback.onCreated(deskId) + createDeskMinimizationRoot(displayId = appearingInDisplayId, deskId = deskId) + return + } + // Check if there's any pending minimization container creation requests under this display. + val deskMinimizationRootRequest = + createDeskMinimizationRootRequests.first { it.displayId == appearingInDisplayId } + val deskId = deskMinimizationRootRequest.deskId + logV("Minimization container for desk #$deskId appeared with id=${taskInfo.taskId}") + val deskMinimizationRoot = DeskMinimizationRoot(deskId, taskInfo, leash) + deskMinimizationRootsByDeskId[deskId] = deskMinimizationRoot + createDeskMinimizationRootRequests.remove(deskMinimizationRootRequest) + hideMinimizationRoot(deskMinimizationRoot) } override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) { - if (roots.contains(taskInfo.taskId)) { + if (deskRootsByDeskId.contains(taskInfo.taskId)) { val deskId = taskInfo.taskId - roots[deskId] = roots[deskId].copy(taskInfo = taskInfo) + deskRootsByDeskId[deskId] = deskRootsByDeskId[deskId].copy(taskInfo = taskInfo) + logV("Desk #$deskId's task info changed") + return } + val minimizationRoot = + deskMinimizationRootsByDeskId.values.find { root -> root.rootId == taskInfo.taskId } + if (minimizationRoot != null) { + deskMinimizationRootsByDeskId.remove(minimizationRoot.deskId) + deskMinimizationRootsByDeskId[minimizationRoot.deskId] = + minimizationRoot.copy(taskInfo = taskInfo) + logV("Minimization root for desk#${minimizationRoot.deskId} task info changed") + return + } + + val parentTaskId = taskInfo.parentTaskId + if (parentTaskId in deskRootsByDeskId) { + val deskId = taskInfo.parentTaskId + val taskId = taskInfo.taskId + logV("onTaskInfoChanged: Task #$taskId appeared in desk #$deskId") + addChildToDesk(taskId = taskId, deskId = deskId) + return + } + // Check whether this task is appearing in a minimization root. + val parentMinimizationRoot = + deskMinimizationRootsByDeskId.values.singleOrNull { it.rootId == parentTaskId } + if (parentMinimizationRoot != null) { + val deskId = parentMinimizationRoot.deskId + val taskId = taskInfo.taskId + logV("onTaskInfoChanged: Task #$taskId was minimized in desk #$deskId ") + addChildToMinimizationRoot(taskId = taskId, deskId = deskId) + return + } + logE("onTaskInfoChanged: unknown task: ${taskInfo.taskId}") } override fun onTaskVanished(taskInfo: RunningTaskInfo) { - if (roots.contains(taskInfo.taskId)) { + if (deskRootsByDeskId.contains(taskInfo.taskId)) { val deskId = taskInfo.taskId - val deskRoot = roots[deskId] + val deskRoot = deskRootsByDeskId[deskId] // Use the last saved taskInfo to obtain the displayId. Using the local one here will // return -1 since the task is not unassociated with a display. val displayId = deskRoot.taskInfo.displayId logV("Desk #$deskId vanished from display #$displayId") - roots.remove(deskId) + deskRootsByDeskId.remove(deskId) + return + } + val deskMinimizationRoot = + deskMinimizationRootsByDeskId.values.singleOrNull { it.rootId == taskInfo.taskId } + if (deskMinimizationRoot != null) { + logV("Minimization root for desk ${deskMinimizationRoot.deskId} vanished") + deskMinimizationRootsByDeskId.remove(deskMinimizationRoot.deskId) return } + + // Check whether the vanishing task was a child of any desk. // At this point, [parentTaskId] may be unset even if this is a task vanishing from a desk, // so search through each root to remove this if it's a child. - roots.forEach { deskId, deskRoot -> + deskRootsByDeskId.forEach { deskId, deskRoot -> if (deskRoot.children.remove(taskInfo.taskId)) { logV("Task #${taskInfo.taskId} vanished from desk #$deskId") return } } + // Check whether the vanishing task was a child of the minimized root and remove it. + deskMinimizationRootsByDeskId.values.forEach { root -> + val taskId = taskInfo.taskId + if (root.children.remove(taskId)) { + logV("Task #$taskId vanished from minimization root of desk #${root.deskId}") + return + } + } + } + + private fun createDeskMinimizationRoot(displayId: Int, deskId: Int) { + createDeskMinimizationRootRequests += + CreateDeskMinimizationRootRequest(displayId = displayId, deskId = deskId) + shellTaskOrganizer.createRootTask( + displayId, + WINDOWING_MODE_FREEFORM, + /* listener = */ this, + /* removeWithTaskOrganizer = */ true, + ) + } + + @SuppressLint("MissingPermission") + private fun hideMinimizationRoot(root: DeskMinimizationRoot) { + shellTaskOrganizer.applyTransaction( + WindowContainerTransaction().apply { setHidden(root.token, /* hidden= */ true) } + ) + } + + private fun addChildToDesk(taskId: Int, deskId: Int) { + deskRootsByDeskId.forEach { _, deskRoot -> + if (deskRoot.deskId == deskId) { + deskRoot.children.add(taskId) + } else { + deskRoot.children.remove(taskId) + } + } + // A task cannot be in both a desk root and a minimization root at the same time, so make + // sure to remove them if needed. + deskMinimizationRootsByDeskId.values.forEach { root -> root.children.remove(taskId) } + } + + private fun addChildToMinimizationRoot(taskId: Int, deskId: Int) { + deskMinimizationRootsByDeskId.forEach { _, minimizationRoot -> + if (minimizationRoot.deskId == deskId) { + minimizationRoot.children += taskId + } else { + minimizationRoot.children -= taskId + } + } + // A task cannot be in both a desk root and a minimization root at the same time, so make + // sure to remove them if needed. + deskRootsByDeskId.forEach { _, deskRoot -> deskRoot.children -= taskId } } @VisibleForTesting @@ -168,34 +342,55 @@ class RootTaskDesksOrganizer( val taskInfo: RunningTaskInfo, val leash: SurfaceControl, val children: MutableSet<Int> = mutableSetOf(), + ) { + val token: WindowContainerToken = taskInfo.token + } + + @VisibleForTesting + data class DeskMinimizationRoot( + val deskId: Int, + val taskInfo: RunningTaskInfo, + val leash: SurfaceControl, + val children: MutableSet<Int> = mutableSetOf(), + ) { + val rootId: Int + get() = taskInfo.taskId + + val token: WindowContainerToken = taskInfo.token + } + + private data class CreateDeskRequest( + val displayId: Int, + val onCreateCallback: OnCreateCallback, ) + private data class CreateDeskMinimizationRootRequest(val displayId: Int, val deskId: Int) + + private fun logV(msg: String, vararg arguments: Any?) { + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + private fun logE(msg: String, vararg arguments: Any?) { + ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + override fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("$prefix$TAG") pw.println("${innerPrefix}Desk Roots:") - roots.forEach { deskId, root -> + deskRootsByDeskId.forEach { deskId, root -> + val minimizationRoot = deskMinimizationRootsByDeskId[deskId] pw.println("$innerPrefix #$deskId visible=${root.taskInfo.isVisible}") + pw.println("$innerPrefix displayId=${root.taskInfo.displayId}") pw.println("$innerPrefix children=${root.children}") - } - } - - private fun addChildToDesk(taskId: Int, deskId: Int) { - roots.forEach { _, deskRoot -> - if (deskRoot.deskId == deskId) { - deskRoot.children.add(taskId) - } else { - deskRoot.children.remove(taskId) + pw.println("$innerPrefix minimization root:") + pw.println("$innerPrefix rootId=${minimizationRoot?.rootId}") + if (minimizationRoot != null) { + pw.println("$innerPrefix children=${minimizationRoot.children}") } } } - private data class CreateRequest(val displayId: Int, val onCreateCallback: OnCreateCallback) - - private fun logV(msg: String, vararg arguments: Any?) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) - } - companion object { private const val TAG = "RootTaskDesksOrganizer" } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md index 3fad28ad232f..a98ae5566394 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md @@ -9,6 +9,7 @@ particular order): 4) [Threading model in the Shell](threading.md) 5) [Making changes in the Shell](changes.md) 6) [Extending the Shell for Products/OEMs](extending.md) +6) [Shell transitions](transitions.md) 7) [Debugging in the Shell](debugging.md) 8) [Testing in the Shell](testing.md) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/transitions.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/transitions.md new file mode 100644 index 000000000000..dc23bb0c77d7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/transitions.md @@ -0,0 +1,118 @@ +# Shell transitions +[Back to home](README.md) + +--- + +## General + +General guides for using Shell Transitions can be found here: +- [Shell transitions animation guide](http://go/shell-transit-anim) +- [Hitchhiker's guide to transitions](http://go/transitions-book) + +## Transient-launch transitions +<span style="color:orange">Use with care!</span> + +Transient-launch transitions are a way to handle non-atomic (ie. gestural) transitions by allowing +WM Core to put participating activities into a transiently visible or hidden state for the duration +of the animation and adding the ability to cancel the transition. + +For example, if you are launching an activity normally, WM Core will be updated +at the start of the animation which includes pausing the previous activity and resuming the next +activity (and subsequently the transition will reconcile that state via an animation). + +If you are transiently launching an activity though, WM Core will ensure that both the leaving +activity and the incoming activity will be RESUMED for the duration of the transition duration. In +addition, WM Core will track the position of the transient-launch activity in the window hierarchy +prior to the launch, and allow Shell to restore it to that position if the transitions needs to be +canceled. + +Starting a transient-launch transition can be done via the activity options (since the activity may +not have been started yet): +```kotlin +val opts = ActivityOptions.makeBasic().setTransientLaunch() +val wct = WindowContainerTransaction() +wct.sendPendingIntent(pendingIntent, new Intent(), opts.toBundle()) +transitions.startTransition(TRANSIT_OPEN, wct, ...) +``` + +And restoring the transient order via a WCT: +```kotlin +val wct = WindowContainerTransaction() +wct.restoreTransientOrder(transientLaunchContainerToken) +transitions.startTransition(TRANSIT_RESTORE, wct, ...) +``` + +### <span style="color:orange">Considerations</span> + +Usage of transient-launch transitions should be done with consideration, there are a few gotchas +that might result in subtle and hard-to-reproduce issues. + +#### Understanding the flow +When starting a transient-launch transition, there are several possible outcomes: +1) The transition finishes as normal: The user is committing the transition to the state requested + at the start of the transition. In such cases, you can simply finish the transition and the + states of the transiently shown/hidden activities will be updated to match the original state + that a non-transient transition would have (ie. closing activities will be stopped). + +2) The transition is interrupted: A change in the system results in the window hierarchy changing + in a way which may or may not affect the transient-launch activity. eg. We transiently-launch + home from app A, but then app B launches. In this case, WM attempts to create a new transition + reflecting the window hierarchy changes (ie. if B occludes Home in the above example, then the + transition will have Home TO_BACK, and B TO_FRONT). + + At this point, the transition handler can choose to merge the incoming transition or not (to + queue it after this current transition). Take note of the next section for concerns re. bookend + transitions. + +3) The transition is canceled: The user is canceling the transition to the previous state. In such + cases, you need to store the `WindowContainerToken` for the task associated with the + transient-launch activity, and restore the transient order via the `WindowContainerTransaction` + API above. In some cases, if anything has been reordered since (ie. due to other merged + transitions), then you may also want to use `WindowContainerTransaction#reorder()` to place all + the relevant containers to their original order (provided via the change-order in the initial + launch transition). + +#### Finishing the transient-launch transition + +When restoring the transient order in the 3rd flow above, it is recommended to do it in a new +transition and <span style="color:orange">**not**</span> via the WindowContainerTransaction in +`TransitionFinishCallback#onTransitionFinished()` provided when starting the transition. + +Changes to the window hierarchy via the finish transaction are not applied in sync with other +transitions that are collecting and aplying, and are also not observable in Shell in any way. +Starting a new transition instead ensures both. (The finish transaction can still be used if there +are non-transition affecting properties (ie. container properties) that need to be updated as a part +of finishing the transient-launch transition). + +So the general idea is when restoring is: + +1) Start transient-launch transition START_T +2) ... +3) All done, start bookend transition END_T +4) Handler receives END_T, merges it and then finishes START_T + +In practice it's not quite that simple, due to the ordering of transitions and extra care must be +taken when using a new transition to prevent deadlocking when merging transitions. + +When a new transition arrives while a transient-launch transition is playing, the handler can +choose to handle/merge the transition into the ongoing one, or skip merging to queue it up to be +played after. In the above flow, we can see how this might result in a deadlock: + +Queueing END during merge: +1) Start transient-launch transition START_T +2) ... +3) Incoming transition OTHER_T, choose to cancel START_T -> start bookend transition END_T, but don't merge OTHER_T +3) Waiting for END_T... <span style="color:red">Deadlock!</span> + +Interrupt while pending END: +1) Start transient-launch transition START_T +2) ... +3) All done, start bookend transition END_T +3) Incoming transition OTHER_T occurs before END_T, but don't merge OTHER_T +3) Waiting for END_T... <span style="color:red">Deadlock!</span> + +This means that when using transient-launch transitions with a bookend transition +<span style="color:orange">requires</span> you to handle any incoming transitions if the bookend is +ever queued (or already posted) after it. You can do so by preempting the bookend transition +(finishing the transient-launch transition), or handling the merge of the new transition (so it +doesn't queue).
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index b60fb5e7bfdd..16e411e1fc07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -25,10 +25,12 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.content.Context; import android.graphics.Rect; +import android.os.Handler; import android.os.IBinder; import android.util.ArrayMap; -import android.util.DisplayMetrics; +import android.util.Log; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -38,6 +40,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.animation.MinimizeAnimator; @@ -52,11 +55,13 @@ import java.util.List; */ public class FreeformTaskTransitionHandler implements Transitions.TransitionHandler, FreeformTaskTransitionStarter { + private static final String TAG = "FreeformTaskTransitionHandler"; private static final int CLOSE_ANIM_DURATION = 400; private final Transitions mTransitions; private final DisplayController mDisplayController; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; + private final Handler mAnimHandler; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); @@ -66,11 +71,13 @@ public class FreeformTaskTransitionHandler Transitions transitions, DisplayController displayController, ShellExecutor mainExecutor, - ShellExecutor animExecutor) { + ShellExecutor animExecutor, + Handler animHandler) { mTransitions = transitions; mDisplayController = displayController; mMainExecutor = mainExecutor; mAnimExecutor = animExecutor; + mAnimHandler = animHandler; } @Override @@ -123,13 +130,11 @@ public class FreeformTaskTransitionHandler @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean transitionHandled = false; final ArrayList<Animator> animations = new ArrayList<>(); - final Runnable onAnimFinish = () -> { + final Runnable onAnimFinish = () -> mMainExecutor.execute(() -> { if (!animations.isEmpty()) return; - mMainExecutor.execute(() -> { - mAnimations.remove(transition); - finishCallback.onTransitionFinished(null /* wct */); - }); - }; + mAnimations.remove(transition); + finishCallback.onTransitionFinished(null /* wct */); + }); for (TransitionInfo.Change change : info.getChanges()) { if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { continue; @@ -234,18 +239,25 @@ public class FreeformTaskTransitionHandler SurfaceControl.Transaction t = new SurfaceControl.Transaction(); SurfaceControl sc = change.getLeash(); finishT.hide(sc); - final DisplayMetrics displayMetrics = - mDisplayController - .getDisplayContext(taskInfo.displayId).getResources().getDisplayMetrics(); + final Context displayContext = + mDisplayController.getDisplayContext(taskInfo.displayId); + if (displayContext == null) { + Log.w(TAG, "No displayContext for displayId=" + taskInfo.displayId); + return false; + } final Animator animator = MinimizeAnimator.create( - displayMetrics, + displayContext, change, t, (anim) -> { - animations.remove(anim); - onAnimFinish.run(); + mMainExecutor.execute(() -> { + animations.remove(anim); + onAnimFinish.run(); + }); return null; - }); + }, + InteractionJankMonitor.getInstance(), + mAnimHandler); animations.add(animator); return true; } @@ -277,8 +289,10 @@ public class FreeformTaskTransitionHandler new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - animations.remove(animator); - onAnimFinish.run(); + mMainExecutor.execute(() -> { + animations.remove(animator); + onAnimFinish.run(); + }); } }); animations.add(animator); 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 99c9302edb75..1ce24f76ada5 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 @@ -404,6 +404,10 @@ public class PipController implements ConfigurationChangeListener, mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams, mPipBoundsAlgorithm); + + // Update the size spec in case aspect ratio is invariant, but display has changed + // since the last PiP session, or this is the first PiP session altogether. + mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio()); return mPipBoundsAlgorithm.getEntryDestinationBounds(); } 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 035c93db7ee4..97b3e5a2da87 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 @@ -738,6 +738,13 @@ public class PipTransition extends PipTransitionController implements } } + if (!mPipTransitionState.isInSwipePipToHomeTransition()) { + // Update the size spec in case aspect ratio is invariant, but display has changed + // since the last PiP session, or this is the first PiP session altogether. + // Skip the update if in swipe PiP to home, as this has already been done. + mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio()); + } + // calculate the entry bounds and notify core to move task to pinned with final bounds final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); mPipBoundsState.setBounds(entryBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 847a0383e7d0..3e03e001c49b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -47,6 +47,7 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.PendingIntent; +import android.app.WindowConfiguration; import android.content.Context; import android.content.Intent; import android.graphics.Color; @@ -75,11 +76,11 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.Flags; -import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.R; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.HomeTransitionObserver; @@ -320,7 +321,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, "RecentsTransitionHandler.mergeAnimation: no controller found"); return; } - controller.merge(info, startT, finishT, mergeTarget, finishCallback); + controller.merge(info, startT, finishT, finishCallback); } @Override @@ -408,7 +409,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // next called. private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel; - // Used to track a pending finish transition + // Used to track a pending finish transition, this is only non-null if + // enableRecentsBookendTransition() is enabled private IBinder mPendingFinishTransition; private IResultReceiver mPendingRunnerFinishCb; @@ -917,7 +919,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, */ @SuppressLint("NewApi") void merge(TransitionInfo info, SurfaceControl.Transaction startT, - SurfaceControl.Transaction finishT, IBinder mergeTarget, + SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback) { if (mFinishCB == null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, @@ -927,16 +929,25 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, return; } - if (Flags.enableRecentsBookendTransition() - && info.getType() == TRANSIT_END_RECENTS_TRANSITION - && mergeTarget == mTransition) { - // This is a pending finish, so merge the end transition to trigger completing the - // cleanup of the recents transition - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION", - mInstanceId); - finishCallback.onTransitionFinished(null /* wct */); - return; + if (Flags.enableRecentsBookendTransition()) { + if (info.getType() == TRANSIT_END_RECENTS_TRANSITION) { + // This is a pending finish, so merge the end transition to trigger completing + // the cleanup of the recents transition + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION", + mInstanceId); + consumeMerge(info, startT, finishT, finishCallback); + return; + } else if (mPendingFinishTransition != null) { + // This transition is interrupting a pending finish that was already sent, so + // pre-empt the pending finish transition since the state has already changed + // in the core + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.merge: Awaiting TRANSIT_END_RECENTS_TRANSITION", + mInstanceId); + onFinishInner(null /* wct */); + return; + } } if (info.getType() == TRANSIT_SLEEP) { @@ -1210,16 +1221,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } return; } + // At this point, we are accepting the merge. - startT.apply(); - // Since we're accepting the merge, update the finish transaction so that changes via - // that transaction will be applied on top of those of the merged transitions - mFinishTransaction = finishT; + consumeMerge(info, startT, finishT, finishCallback); + + // Notify Launcher of the new opening tasks if necessary boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue(); - if (!passTransitionInfo) { - // not using the incoming anim-only surfaces - info.releaseAnimSurfaces(); - } if (appearedTargets != null) { try { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, @@ -1229,6 +1236,27 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, Slog.e(TAG, "Error sending appeared tasks to recents animation", e); } } + } + + /** + * Consumes the merge of the other given transition. + */ + private void consumeMerge(TransitionInfo info, SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT, + Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.merge: consuming merge", + mInstanceId); + + startT.apply(); + // Since we're accepting the merge, update the finish transaction so that changes via + // that transaction will be applied on top of those of the merged transitions + mFinishTransaction = finishT; + boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue(); + if (!passTransitionInfo) { + // not using the incoming anim-only surfaces + info.releaseAnimSurfaces(); + } finishCallback.onTransitionFinished(null /* wct */); } @@ -1346,9 +1374,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, final SurfaceControl.Transaction t = mFinishTransaction; final WindowContainerTransaction wct = new WindowContainerTransaction(); + // The following code must set this if it is changing anything in core that might affect + // transitions as a part of finishing the recents transition + boolean requiresBookendTransition = false; + if (mKeyguardLocked && mRecentsTask != null) { if (toHome) wct.reorder(mRecentsTask, true /* toTop */); else wct.restoreTransientOrder(mRecentsTask); + // We are manipulating the window hierarchy, which should only be done with the + // bookend transition + requiresBookendTransition = true; } if (returningToApp) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); @@ -1365,6 +1400,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, if (!mKeyguardLocked && mRecentsTask != null) { wct.restoreTransientOrder(mRecentsTask); } + // We are manipulating the window hierarchy, which should only be done with the + // bookend transition + requiresBookendTransition = true; } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " 3p launching home"); // Special situation where 3p launcher was changed during recents (this happens @@ -1384,6 +1422,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, if (!mKeyguardLocked && mRecentsTask != null) { wct.restoreTransientOrder(mRecentsTask); } + // We are manipulating the window hierarchy, which should only be done with the + // bookend transition + requiresBookendTransition = true; } else { if (mPausingSeparateHome) { if (mOpeningTasks.isEmpty()) { @@ -1484,13 +1525,21 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, if (Flags.enableRecentsBookendTransition()) { if (!wct.isEmpty()) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - "[%d] RecentsController.finishInner: " - + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId); - mPendingRunnerFinishCb = runnerFinishCb; - mPendingFinishTransition = mTransitions.startTransition( - TRANSIT_END_RECENTS_TRANSITION, wct, - new PendingFinishTransitionHandler()); + if (requiresBookendTransition) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.finishInner: " + + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId); + mPendingRunnerFinishCb = runnerFinishCb; + mPendingFinishTransition = mTransitions.startTransition( + TRANSIT_END_RECENTS_TRANSITION, wct, + new PendingFinishTransitionHandler()); + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.finishInner: Non-transition affecting wct", + mInstanceId); + mPendingRunnerFinishCb = runnerFinishCb; + onFinishInner(wct); + } } else { // If there's no work to do, just go ahead and clean up mPendingRunnerFinishCb = runnerFinishCb; @@ -1631,6 +1680,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] PendingFinishTransitionHandler.startAnimation: " + + "Started pending finish transition", mInstanceId); return false; } @@ -1644,10 +1696,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, @Override public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction) { + if (mPendingFinishTransition == null) { + // The cleanup was pre-empted by an earlier transition, nothing there is nothing + // to do here + return; + } // Once we have merged (or not if the WCT didn't result in any changes), then we can // run the pending finish logic ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - "[%d] RecentsController.onTransitionConsumed: " + "[%d] PendingFinishTransitionHandler.onTransitionConsumed: " + "Consumed pending finish transition", mInstanceId); onFinishInner(null /* wct */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 743bd052995e..347dcff86529 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -40,7 +40,6 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.protolog.ProtoLog; -import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.ComponentUtils; @@ -439,9 +438,6 @@ public class DefaultMixedHandler implements MixedTransitionHandler, for (int i = 0; i < info.getRootCount(); ++i) { out.addRoot(info.getRoot(i)); } - if (!Flags.moveAnimationOptionsToChange()) { - out.setAnimationOptions(info.getAnimationOptions()); - } return out; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 01428e60582e..e9c6adec75d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -539,7 +539,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { cornerRadius = 0; } - backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a, + backgroundColorForTransition = getTransitionBackgroundColorIfSet(change, a, backgroundColorForTransition); if (!isTask && a.getExtensionEdges() != 0x0) { @@ -606,12 +606,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { mTransactionPool, mMainExecutor, animRelOffset, cornerRadius, clipRect); - final TransitionInfo.AnimationOptions options; - if (Flags.moveAnimationOptionsToChange()) { - options = change.getAnimationOptions(); - } else { - options = info.getAnimationOptions(); - } + final TransitionInfo.AnimationOptions options = change.getAnimationOptions(); if (options != null) { attachThumbnail(animations, onAnimFinish, change, options, cornerRadius); } @@ -834,12 +829,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final boolean isOpeningType = TransitionUtil.isOpeningType(type); final boolean enter = TransitionUtil.isOpeningType(changeMode); final boolean isTask = change.getTaskInfo() != null; - final TransitionInfo.AnimationOptions options; - if (Flags.moveAnimationOptionsToChange()) { - options = change.getAnimationOptions(); - } else { - options = info.getAnimationOptions(); - } + final TransitionInfo.AnimationOptions options = change.getAnimationOptions(); final int overrideType = options != null ? options.getType() : ANIM_NONE; final int userId = options != null ? options.getUserId() : UserHandle.USER_CURRENT; final Rect endBounds = TransitionUtil.isClosingType(changeMode) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java index 1917996d48fb..938885cc1684 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java @@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE; import static com.android.wm.shell.transition.Transitions.TransitionObserver; import android.annotation.NonNull; @@ -35,6 +36,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.shared.IHomeTransitionListener; import com.android.wm.shell.shared.TransitionUtil; +import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; /** * The {@link TransitionObserver} that observes for transitions involving the home @@ -48,6 +50,8 @@ public class HomeTransitionObserver implements TransitionObserver, private @NonNull final Context mContext; private @NonNull final ShellExecutor mMainExecutor; + private Boolean mPendingHomeVisibilityUpdate; + public HomeTransitionObserver(@NonNull Context context, @NonNull ShellExecutor mainExecutor) { mContext = context; @@ -59,28 +63,78 @@ public class HomeTransitionObserver implements TransitionObserver, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction) { + if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { + handleTransitionReadyWithBubbleAnything(info); + } else { + handleTransitionReady(info); + } + } + + private void handleTransitionReady(@NonNull TransitionInfo info) { for (TransitionInfo.Change change : info.getChanges()) { final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP - || taskInfo == null + if (taskInfo == null + || info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP || taskInfo.displayId != DEFAULT_DISPLAY || taskInfo.taskId == -1 || !taskInfo.isRunning) { continue; } + Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info, change, taskInfo); + if (homeVisibilityUpdate != null) { + notifyHomeVisibilityChanged(homeVisibilityUpdate); + } + } + } + + private void handleTransitionReadyWithBubbleAnything(@NonNull TransitionInfo info) { + Boolean homeVisibilityUpdate = null; + for (TransitionInfo.Change change : info.getChanges()) { + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo == null + || taskInfo.displayId != DEFAULT_DISPLAY + || taskInfo.taskId == -1 + || !taskInfo.isRunning) { + continue; + } + + Boolean update = getHomeVisibilityUpdate(info, change, taskInfo); + if (update != null) { + homeVisibilityUpdate = update; + } + } + + if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) { + // Do not apply at the start of desktop drag as that updates launcher UI visibility. + // Store the value and apply with a next transition if needed. + mPendingHomeVisibilityUpdate = homeVisibilityUpdate; + return; + } + + if (info.getType() == TRANSIT_CONVERT_TO_BUBBLE && homeVisibilityUpdate == null) { + // We are converting to bubble and we did not get a change to home visibility in this + // transition. Apply the value from start of drag. + homeVisibilityUpdate = mPendingHomeVisibilityUpdate; + } + if (homeVisibilityUpdate != null) { + mPendingHomeVisibilityUpdate = null; + notifyHomeVisibilityChanged(homeVisibilityUpdate); + } + } - final int mode = change.getMode(); - final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED); - if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) { - final boolean gestureToHomeTransition = isBackGesture - && TransitionUtil.isClosingType(info.getType()); - if (gestureToHomeTransition || TransitionUtil.isClosingMode(mode) - || (!isBackGesture && TransitionUtil.isOpeningMode(mode))) { - notifyHomeVisibilityChanged(gestureToHomeTransition - || TransitionUtil.isOpeningType(mode)); - } + private Boolean getHomeVisibilityUpdate(TransitionInfo info, + TransitionInfo.Change change, ActivityManager.RunningTaskInfo taskInfo) { + final int mode = change.getMode(); + final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED); + if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) { + final boolean gestureToHomeTransition = isBackGesture + && TransitionUtil.isClosingType(info.getType()); + if (gestureToHomeTransition || TransitionUtil.isClosingMode(mode) + || (!isBackGesture && TransitionUtil.isOpeningMode(mode))) { + return gestureToHomeTransition || TransitionUtil.isOpeningType(mode); } } + return null; } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 4feb4753096e..7984bcedc4e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -58,7 +58,6 @@ import android.window.TransitionInfo; import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.ProtoLog; -import com.android.window.flags.Flags; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.TransitionUtil; @@ -78,12 +77,7 @@ public class TransitionAnimationHelper { final boolean isFreeform = isTask && change.getTaskInfo().isFreeform(); final boolean isCoveredByOpaqueFullscreenChange = isCoveredByOpaqueFullscreenChange(info, change); - final TransitionInfo.AnimationOptions options; - if (Flags.moveAnimationOptionsToChange()) { - options = change.getAnimationOptions(); - } else { - options = info.getAnimationOptions(); - } + final TransitionInfo.AnimationOptions options = change.getAnimationOptions(); final int overrideType = options != null ? options.getType() : ANIM_NONE; int animAttr = 0; boolean translucent = false; @@ -279,16 +273,10 @@ public class TransitionAnimationHelper { * the given transition animation. */ @ColorInt - public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo info, - @NonNull TransitionInfo.Change change, @NonNull Animation a, - @ColorInt int defaultColor) { + public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo.Change change, + @NonNull Animation a, @ColorInt int defaultColor) { if (!a.getShowBackdrop()) { return defaultColor; - } - if (!Flags.moveAnimationOptionsToChange() && info.getAnimationOptions() != null - && info.getAnimationOptions().getBackgroundColor() != 0) { - // If available use the background color provided through AnimationOptions - return info.getAnimationOptions().getBackgroundColor(); } else if (a.getBackdropColor() != 0) { // Otherwise fallback on the background color provided through the animation // definition. 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 5a6ea214e561..cf139a008164 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 @@ -103,6 +103,7 @@ import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController; import com.android.wm.shell.common.MultiInstanceHelper; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; @@ -258,6 +259,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final RecentsTransitionHandler mRecentsTransitionHandler; private final DesktopModeCompatPolicy mDesktopModeCompatPolicy; private final DesktopTilingDecorViewModel mDesktopTilingDecorViewModel; + private final MultiDisplayDragMoveIndicatorController mMultiDisplayDragMoveIndicatorController; public DesktopModeWindowDecorViewModel( Context context, @@ -296,7 +298,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, WindowDecorTaskResourceLoader taskResourceLoader, RecentsTransitionHandler recentsTransitionHandler, DesktopModeCompatPolicy desktopModeCompatPolicy, - DesktopTilingDecorViewModel desktopTilingDecorViewModel) { + DesktopTilingDecorViewModel desktopTilingDecorViewModel, + MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) { this( context, shellExecutor, @@ -340,7 +343,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy, - desktopTilingDecorViewModel); + desktopTilingDecorViewModel, + multiDisplayDragMoveIndicatorController); } @VisibleForTesting @@ -387,7 +391,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, WindowDecorTaskResourceLoader taskResourceLoader, RecentsTransitionHandler recentsTransitionHandler, DesktopModeCompatPolicy desktopModeCompatPolicy, - DesktopTilingDecorViewModel desktopTilingDecorViewModel) { + DesktopTilingDecorViewModel desktopTilingDecorViewModel, + MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -460,6 +465,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mDesktopModeCompatPolicy = desktopModeCompatPolicy; mDesktopTilingDecorViewModel = desktopTilingDecorViewModel; mDesktopTasksController.setSnapEventHandler(this); + mMultiDisplayDragMoveIndicatorController = multiDisplayDragMoveIndicatorController; shellInit.addInitCallback(this::onInit, this); } @@ -1759,7 +1765,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mTransitions, mInteractionJankMonitor, mTransactionFactory, - mMainHandler); + mMainHandler, + mMultiDisplayDragMoveIndicatorController); windowDecoration.setTaskDragResizer(taskPositioner); final DesktopModeTouchEventListener touchEventListener = @@ -2056,7 +2063,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, Transitions transitions, InteractionJankMonitor interactionJankMonitor, Supplier<SurfaceControl.Transaction> transactionFactory, - Handler handler) { + Handler handler, + MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) { final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled() // TODO(b/383632995): Update when the flag is launched. ? (Flags.enableConnectedDisplaysWindowDrag() @@ -2067,7 +2075,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, dragEventListener, transitions, interactionJankMonitor, - handler) + handler, + multiDisplayDragMoveIndicatorController) : new VeiledResizeTaskPositioner( taskOrganizer, windowDecoration, 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 dca376f7df0e..6165dbf686fd 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 @@ -1069,7 +1069,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private static int getCornerRadius(@NonNull Context context, int layoutResId) { if (layoutResId == R.layout.desktop_mode_app_header) { return loadDimensionPixelSize(context.getResources(), - R.dimen.desktop_windowing_freeform_rounded_corner_radius); + com.android.wm.shell.shared.R.dimen + .desktop_windowing_freeform_rounded_corner_radius); } return INVALID_CORNER_RADIUS; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt index bb20292a51d4..c6cb62d153ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.windowdecor import android.graphics.PointF import android.graphics.Rect +import android.hardware.display.DisplayTopology import android.os.Handler import android.os.IBinder import android.os.Looper @@ -32,10 +33,10 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator +import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions import java.util.concurrent.TimeUnit -import java.util.function.Supplier /** * A task positioner that also takes into account resizing a @@ -49,11 +50,12 @@ class MultiDisplayVeiledResizeTaskPositioner( private val desktopWindowDecoration: DesktopModeWindowDecoration, private val displayController: DisplayController, dragEventListener: DragPositioningCallbackUtility.DragEventListener, - private val transactionSupplier: Supplier<SurfaceControl.Transaction>, + private val transactionSupplier: () -> SurfaceControl.Transaction, private val transitions: Transitions, private val interactionJankMonitor: InteractionJankMonitor, @ShellMainThread private val handler: Handler, -) : TaskPositioner, Transitions.TransitionHandler { + private val multiDisplayDragMoveIndicatorController: MultiDisplayDragMoveIndicatorController, +) : TaskPositioner, Transitions.TransitionHandler, DisplayController.OnDisplaysChangedListener { private val dragEventListeners = mutableListOf<DragPositioningCallbackUtility.DragEventListener>() private val stableBounds = Rect() @@ -71,6 +73,7 @@ class MultiDisplayVeiledResizeTaskPositioner( private var isResizingOrAnimatingResize = false @Surface.Rotation private var rotation = 0 private var startDisplayId = 0 + private val displayIds = mutableSetOf<Int>() constructor( taskOrganizer: ShellTaskOrganizer, @@ -80,19 +83,22 @@ class MultiDisplayVeiledResizeTaskPositioner( transitions: Transitions, interactionJankMonitor: InteractionJankMonitor, @ShellMainThread handler: Handler, + multiDisplayDragMoveIndicatorController: MultiDisplayDragMoveIndicatorController, ) : this( taskOrganizer, windowDecoration, displayController, dragEventListener, - Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() }, + { SurfaceControl.Transaction() }, transitions, interactionJankMonitor, handler, + multiDisplayDragMoveIndicatorController, ) init { dragEventListeners.add(dragEventListener) + displayController.addDisplayWindowListener(this) } override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect { @@ -164,7 +170,7 @@ class MultiDisplayVeiledResizeTaskPositioner( createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) ) - val t = transactionSupplier.get() + val t = transactionSupplier() val startDisplayLayout = displayController.getDisplayLayout(startDisplayId) val currentDisplayLayout = displayController.getDisplayLayout(displayId) @@ -196,7 +202,13 @@ class MultiDisplayVeiledResizeTaskPositioner( ) ) - // TODO(b/383069173): Render drag indicator(s) + multiDisplayDragMoveIndicatorController.onDragMove( + boundsDp, + startDisplayId, + desktopWindowDecoration.mTaskInfo, + displayIds, + transactionSupplier, + ) t.setPosition( desktopWindowDecoration.leash, @@ -267,7 +279,10 @@ class MultiDisplayVeiledResizeTaskPositioner( ) ) - // TODO(b/383069173): Clear drag indicator(s) + multiDisplayDragMoveIndicatorController.onDragEnd( + desktopWindowDecoration.mTaskInfo.taskId, + transactionSupplier, + ) } interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) @@ -348,6 +363,14 @@ class MultiDisplayVeiledResizeTaskPositioner( dragEventListeners.remove(dragEventListener) } + override fun onTopologyChanged(topology: DisplayTopology) { + // TODO: b/383069173 - Cancel window drag when topology changes happen during drag. + + displayIds.clear() + val displayBounds = topology.getAbsoluteBounds() + displayIds.addAll(List(displayBounds.size()) { displayBounds.keyAt(it) }) + } + companion object { // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid // timing out in the middle of a resize or drag action. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 94dc774a6737..d4d8d93abf7d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -39,7 +39,6 @@ import android.animation.Animator; import android.annotation.NonNull; import android.graphics.Point; import android.graphics.Rect; -import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; @@ -77,7 +76,6 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim doNothing().when(mController).onAnimationFinished(any()); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testStartAnimation() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) @@ -103,7 +101,6 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim verify(mController).onAnimationFinished(mTransition); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testChangesBehindStartingWindow() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) @@ -118,7 +115,6 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim assertEquals(0, animator.getDuration()); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testTransitionTypeDragResize() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TASK_FRAGMENT_DRAG_RESIZE, 0) @@ -133,25 +129,6 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim assertEquals(0, animator.getDuration()); } - @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) - @Test - public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() { - final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) - .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN)) - .build(); - info.setAnimationOptions(TransitionInfo.AnimationOptions - .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, - 0 /* backgroundColor */, false /* overrideTaskTransition */)); - final Animator animator = mAnimRunner.createAnimator( - info, mStartTransaction, mFinishTransaction, - () -> mFinishCallback.onTransitionFinished(null /* wct */), - new ArrayList<>()); - - // An invalid custom animation is equivalent to jump-cut. - assertEquals(0, animator.getDuration()); - } - - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) @@ -169,36 +146,6 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim assertEquals(0, animator.getDuration()); } - @DisableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) - @Test - public void testCalculateParentBounds_flagDisabled() { - final Rect parentBounds = new Rect(0, 0, 2000, 2000); - final Rect primaryBounds = new Rect(); - final Rect secondaryBounds = new Rect(); - parentBounds.splitVertically(primaryBounds, secondaryBounds); - - final TransitionInfo.Change change = createChange(0 /* flags */); - change.setStartAbsBounds(secondaryBounds); - - final TransitionInfo.Change boundsAnimationChange = createChange(0 /* flags */); - boundsAnimationChange.setStartAbsBounds(primaryBounds); - boundsAnimationChange.setEndAbsBounds(primaryBounds); - final Rect actualParentBounds = new Rect(); - - calculateParentBounds(change, boundsAnimationChange, actualParentBounds); - - assertEquals(parentBounds, actualParentBounds); - - actualParentBounds.setEmpty(); - - boundsAnimationChange.setStartAbsBounds(secondaryBounds); - boundsAnimationChange.setEndAbsBounds(primaryBounds); - - calculateParentBounds(boundsAnimationChange, boundsAnimationChange, actualParentBounds); - - assertEquals(parentBounds, actualParentBounds); - } - // TODO(b/243518738): Rewrite with TestParameter @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index 9f29ef71930a..53a13d0d4ffd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -32,8 +32,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import android.animation.Animator; import android.animation.ValueAnimator; import android.graphics.Rect; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -41,7 +39,6 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.window.flags.Flags; import com.android.wm.shell.transition.TransitionInfoBuilder; import org.junit.Before; @@ -69,13 +66,11 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation any()); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testInstantiate() { verify(mShellInit).addInitCallback(any(), any()); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testOnInit() { mController.onInit(); @@ -83,7 +78,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation verify(mTransitions).addHandler(mController); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testSetAnimScaleSetting() { mController.setAnimScaleSetting(1.0f); @@ -92,7 +86,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation verify(mAnimSpec).setAnimScaleSetting(1.0f); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testStartAnimation_containsNonActivityEmbeddingChange() { final TransitionInfo.Change nonEmbeddedOpen = createChange(0 /* flags */); @@ -129,7 +122,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation assertFalse(info2.getChanges().contains(nonEmbeddedClose)); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) @@ -146,7 +138,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation verifyNoMoreInteractions(mFinishCallback); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testStartAnimation_containsActivityEmbeddingSplitChange() { // Change that occupies only part of the Task. @@ -164,7 +155,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation verifyNoMoreInteractions(mFinishTransaction); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() { // Change that is entering ActivityEmbedding split. @@ -181,7 +171,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation verifyNoMoreInteractions(mFinishTransaction); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() { // Change that is exiting ActivityEmbedding split. @@ -198,27 +187,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation verifyNoMoreInteractions(mFinishTransaction); } - @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) - @Test - public void testShouldAnimate_containsAnimationOptions_disableAnimOptionsPerChange() { - final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0) - .addChange(createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS, TASK_BOUNDS)) - .build(); - - info.setAnimationOptions(TransitionInfo.AnimationOptions - .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, - 0 /* backgroundColor */, false /* overrideTaskTransition */)); - assertTrue(mController.shouldAnimate(info)); - - info.setAnimationOptions(TransitionInfo.AnimationOptions - .makeSceneTransitionAnimOptions()); - assertFalse(mController.shouldAnimate(info)); - - info.setAnimationOptions(TransitionInfo.AnimationOptions.makeCrossProfileAnimOptions()); - assertFalse(mController.shouldAnimate(info)); - } - - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testShouldAnimate_containsAnimationOptions_enableAnimOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0) @@ -239,7 +207,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation assertFalse(mController.shouldAnimate(info)); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @UiThreadTest @Test public void testMergeAnimation() { @@ -278,7 +245,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation verify(mFinishCallback).onTransitionFinished(any()); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testOnAnimationFinished() { // Should not call finish when there is no transition. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt new file mode 100644 index 000000000000..abd238847519 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt @@ -0,0 +1,168 @@ +/* + * 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.wm.shell.common + +import android.app.ActivityManager.RunningTaskInfo +import android.content.res.Configuration +import android.graphics.Rect +import android.graphics.RectF +import android.testing.TestableResources +import android.view.Display +import android.view.SurfaceControl +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestShellExecutor +import java.util.function.Supplier +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.whenever + +/** + * Tests for [MultiDisplayDragMoveIndicatorController]. + * + * Build/Install/Run: atest WMShellUnitTests:MultiDisplayDragMoveIndicatorControllerTest + */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { + private val displayController = mock<DisplayController>() + private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>() + private val indicatorSurfaceFactory = mock<MultiDisplayDragMoveIndicatorSurface.Factory>() + private val indicatorSurface0 = mock<MultiDisplayDragMoveIndicatorSurface>() + private val indicatorSurface1 = mock<MultiDisplayDragMoveIndicatorSurface>() + private val transaction = mock<SurfaceControl.Transaction>() + private val transactionSupplier = mock<Supplier<SurfaceControl.Transaction>>() + private val taskInfo = mock<RunningTaskInfo>() + private val display0 = mock<Display>() + private val display1 = mock<Display>() + + private lateinit var resources: TestableResources + private val executor = TestShellExecutor() + + private lateinit var controller: MultiDisplayDragMoveIndicatorController + + @Before + fun setUp() { + resources = mContext.getOrCreateTestableResources() + val resourceConfiguration = Configuration() + resourceConfiguration.uiMode = 0 + resources.overrideConfiguration(resourceConfiguration) + + controller = + MultiDisplayDragMoveIndicatorController( + displayController, + rootTaskDisplayAreaOrganizer, + indicatorSurfaceFactory, + executor, + ) + + val spyDisplayLayout0 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0, + MultiDisplayTestUtil.DISPLAY_DPI_0, + resources.resources, + ) + val spyDisplayLayout1 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1, + MultiDisplayTestUtil.DISPLAY_DPI_1, + resources.resources, + ) + + taskInfo.taskId = TASK_ID + whenever(displayController.getDisplayLayout(0)).thenReturn(spyDisplayLayout0) + whenever(displayController.getDisplayLayout(1)).thenReturn(spyDisplayLayout1) + whenever(displayController.getDisplay(0)).thenReturn(display0) + whenever(displayController.getDisplay(1)).thenReturn(display1) + whenever(indicatorSurfaceFactory.create(taskInfo, display0)).thenReturn(indicatorSurface0) + whenever(indicatorSurfaceFactory.create(taskInfo, display1)).thenReturn(indicatorSurface1) + whenever(transactionSupplier.get()).thenReturn(transaction) + } + + @Test + fun onDrag_boundsNotIntersectWithDisplay_noIndicator() { + controller.onDragMove( + RectF(2000f, 2000f, 2100f, 2200f), // not intersect with any display + startDisplayId = 0, + taskInfo, + displayIds = setOf(0, 1), + ) { transaction } + executor.flushAll() + + verify(indicatorSurfaceFactory, never()).create(any(), any()) + } + + @Test + fun onDrag_boundsIntersectWithStartDisplay_noIndicator() { + controller.onDragMove( + RectF(100f, 100f, 200f, 200f), // intersect with display 0 + startDisplayId = 0, + taskInfo, + displayIds = setOf(0, 1), + ) { transaction } + executor.flushAll() + + verify(indicatorSurfaceFactory, never()).create(any(), any()) + } + + @Test + fun onDrag_boundsIntersectWithNonStartDisplay_showAndDisposeIndicator() { + controller.onDragMove( + RectF(100f, -100f, 200f, 200f), // intersect with display 0 and 1 + startDisplayId = 0, + taskInfo, + displayIds = setOf(0, 1), + ) { transaction } + executor.flushAll() + + verify(indicatorSurfaceFactory, times(1)).create(taskInfo, display1) + verify(indicatorSurface1, times(1)) + .show(transaction, taskInfo, rootTaskDisplayAreaOrganizer, 1, Rect(0, 1800, 200, 2400)) + + controller.onDragMove( + RectF(2000f, 2000f, 2100f, 2200f), // not intersect with display 1 + startDisplayId = 0, + taskInfo, + displayIds = setOf(0, 1) + ) { transaction } + while (executor.callbacks.isNotEmpty()) { + executor.flushAll() + } + + verify(indicatorSurface1, times(1)) + .relayout(any(), eq(transaction), shouldBeVisible = eq(false)) + + controller.onDragEnd(TASK_ID, { transaction }) + while (executor.callbacks.isNotEmpty()) { + executor.flushAll() + } + + verify(indicatorSurface1, times(1)).disposeSurface(transaction) + } + + companion object { + private const val TASK_ID = 10 + } +} 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 0d5741fccbcc..8ad54f5a0bb4 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,51 +16,28 @@ 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 -import android.provider.Settings -import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY -import android.view.IWindowManager -import android.view.WindowManager.TRANSIT_CHANGE -import android.window.DisplayAreaInfo -import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never 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 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit -import com.android.wm.shell.transition.Transitions -import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.isNull import org.mockito.Mock -import org.mockito.Mockito.anyInt import org.mockito.Mockito.spy -import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.eq import org.mockito.kotlin.whenever import org.mockito.quality.Strictness @@ -73,27 +50,18 @@ import org.mockito.quality.Strictness @RunWith(AndroidTestingRunner::class) class DesktopDisplayEventHandlerTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor - @Mock lateinit var transitions: Transitions @Mock lateinit var displayController: DisplayController - @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer - @Mock private lateinit var mockWindowManager: IWindowManager @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories @Mock private lateinit var mockDesktopRepository: DesktopRepository @Mock private lateinit var mockDesktopTasksController: DesktopTasksController - @Mock private lateinit var shellTaskOrganizer: ShellTaskOrganizer + @Mock private lateinit var desktopDisplayModeController: DesktopDisplayModeController 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() { @@ -105,24 +73,15 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { shellInit = spy(ShellInit(testExecutor)) whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository) - whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } - whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) - .thenReturn(defaultTDA) handler = DesktopDisplayEventHandler( context, shellInit, - transitions, displayController, - rootTaskDisplayAreaOrganizer, - mockWindowManager, mockDesktopUserRepositories, mockDesktopTasksController, - shellTaskOrganizer, + desktopDisplayModeController, ) - whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } - runningTasks.add(freeformTask) - runningTasks.add(fullscreenTask) shellInit.init() verify(displayController) .addDisplayWindowListener(onDisplaysChangedListenerCaptor.capture()) @@ -133,65 +92,6 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { mockitoSession.finishMocking() } - private fun testDisplayWindowingModeSwitch( - defaultWindowingMode: Int, - extendedDisplayEnabled: Boolean, - expectTransition: Boolean, - ) { - defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode - whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode } - val settingsSession = - ExtendedDisplaySettingsSession( - context.contentResolver, - if (extendedDisplayEnabled) 1 else 0, - ) - - settingsSession.use { - 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[defaultTDA.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) - .isEqualTo(defaultWindowingMode) - } else { - verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) - } - } - } - - @Test - fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() { - testDisplayWindowingModeSwitch( - defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, - extendedDisplayEnabled = false, - expectTransition = false, - ) - } - - @Test - fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() { - testDisplayWindowingModeSwitch( - defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, - extendedDisplayEnabled = true, - expectTransition = true, - ) - } - - @Test - fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() { - testDisplayWindowingModeSwitch( - defaultWindowingMode = WINDOWING_MODE_FREEFORM, - extendedDisplayEnabled = true, - expectTransition = false, - ) - } - @Test fun testDisplayAdded_supportsDesks_createsDesk() { whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) @@ -231,70 +131,14 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { } @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)) + fun testConnectExternalDisplay() { onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId) + verify(desktopDisplayModeController).refreshDisplayWindowingMode() } - private fun disconnectExternalDisplay() { - whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) - .thenReturn(intArrayOf(DEFAULT_DISPLAY)) + @Test + fun testDisconnectExternalDisplay() { onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId) - } - - private class ExtendedDisplaySettingsSession( - private val contentResolver: ContentResolver, - private val overrideValue: Int, - ) : AutoCloseable { - private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS - private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0) - - init { - Settings.Global.putInt(contentResolver, settingName, overrideValue) - } - - override fun close() { - Settings.Global.putInt(contentResolver, settingName, initialValue) - } + verify(desktopDisplayModeController).refreshDisplayWindowingMode() } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt new file mode 100644 index 000000000000..0ff7230f6e0c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt @@ -0,0 +1,233 @@ +/* + * 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.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.provider.Settings +import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS +import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY +import android.view.IWindowManager +import android.view.WindowManager.TRANSIT_CHANGE +import android.window.DisplayAreaInfo +import android.window.WindowContainerTransaction +import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.never +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.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider +import com.android.wm.shell.transition.Transitions +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.isNull +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +/** + * Test class for [DesktopDisplayModeController] + * + * Usage: atest WMShellUnitTests:DesktopDisplayModeControllerTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopDisplayModeControllerTest : ShellTestCase() { + private val transitions = mock<Transitions>() + private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>() + private val mockWindowManager = mock<IWindowManager>() + private val shellTaskOrganizer = mock<ShellTaskOrganizer>() + private val desktopWallpaperActivityTokenProvider = + mock<DesktopWallpaperActivityTokenProvider>() + + private lateinit var controller: DesktopDisplayModeController + + private val runningTasks = mutableListOf<RunningTaskInfo>() + 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) + private val wallpaperToken = MockToken().token() + + @Before + fun setUp() { + whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder()) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) + .thenReturn(defaultTDA) + controller = + DesktopDisplayModeController( + context, + transitions, + rootTaskDisplayAreaOrganizer, + mockWindowManager, + shellTaskOrganizer, + desktopWallpaperActivityTokenProvider, + ) + runningTasks.add(freeformTask) + runningTasks.add(fullscreenTask) + whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks)) + whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken) + } + + private fun testDisplayWindowingModeSwitch( + defaultWindowingMode: Int, + extendedDisplayEnabled: Boolean, + expectTransition: Boolean, + ) { + defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode + whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(defaultWindowingMode) + val settingsSession = + ExtendedDisplaySettingsSession( + context.contentResolver, + if (extendedDisplayEnabled) 1 else 0, + ) + + settingsSession.use { + 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[defaultTDA.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) + .isEqualTo(defaultWindowingMode) + assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } else { + verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) + } + } + } + + @Test + fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() { + testDisplayWindowingModeSwitch( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + extendedDisplayEnabled = false, + expectTransition = false, + ) + } + + @Test + fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() { + testDisplayWindowingModeSwitch( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + extendedDisplayEnabled = true, + expectTransition = true, + ) + } + + @Test + fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() { + testDisplayWindowingModeSwitch( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + extendedDisplayEnabled = true, + expectTransition = false, + ) + } + + @Test + fun displayWindowingModeSwitch_existingTasksOnConnected() { + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(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, EXTERNAL_DISPLAY_ID)) + controller.refreshDisplayWindowingMode() + } + + private fun disconnectExternalDisplay() { + whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) + .thenReturn(intArrayOf(DEFAULT_DISPLAY)) + controller.refreshDisplayWindowingMode() + } + + private class ExtendedDisplaySettingsSession( + private val contentResolver: ContentResolver, + private val overrideValue: Int, + ) : AutoCloseable { + private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS + private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0) + + init { + Settings.Global.putInt(contentResolver, settingName, overrideValue) + } + + override fun close() { + Settings.Global.putInt(contentResolver, settingName, initialValue) + } + } + + private companion object { + const val EXTERNAL_DISPLAY_ID = 100 + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt index 4c3325d4d1de..0d1c57221fb9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt @@ -21,6 +21,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WindowingMode +import android.os.Handler import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.SurfaceControl @@ -56,7 +57,12 @@ class DesktopMinimizationTransitionHandlerTest : ShellTestCase() { @Before fun setUp() { handler = - DesktopMinimizationTransitionHandler(testExecutor, testExecutor, displayController) + DesktopMinimizationTransitionHandler( + testExecutor, + testExecutor, + displayController, + mock<Handler>(), + ) whenever(displayController.getDisplayContext(any())).thenReturn(mContext) } 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 ed9b97d264f7..9bff287e314a 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 @@ -333,7 +333,7 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { @Test fun isOnlyVisibleNonClosingTask_singleVisibleClosingTask() { repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true) - repo.addClosingTask(DEFAULT_DISPLAY, 1) + repo.addClosingTask(displayId = DEFAULT_DISPLAY, deskId = 0, taskId = 1) // A visible task that's closing assertThat(repo.isVisibleTask(1)).isTrue() 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 fcd92ac2678a..785fb3e875b8 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 @@ -2477,8 +2477,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN) val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) - assertThat(wct.hierarchyOps).hasSize(1) - wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + wct.assertLaunchTask(task.taskId, WINDOWING_MODE_FREEFORM) } @Test @@ -2827,7 +2826,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() fun onDesktopWindowClose_singleActiveTask_isClosing() { val task = setUpFreeformTask() - taskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId) + taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, deskId = 0, taskId = task.taskId) val wct = WindowContainerTransaction() controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) @@ -2864,7 +2863,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task1 = setUpFreeformTask() val task2 = setUpFreeformTask() - taskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId) + taskRepository.addClosingTask( + displayId = DEFAULT_DISPLAY, + deskId = 0, + taskId = task2.taskId, + ) val wct = WindowContainerTransaction() controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1) @@ -3225,6 +3228,30 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun onDesktopWindowMinimize_minimizesTask() { + val task = setUpFreeformTask() + val transition = Binder() + val runOnTransit = RunOnStartTransitionCallback() + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) + .thenReturn(transition) + whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any())) + .thenReturn( + ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit) + ) + + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) + + verify(desksOrganizer).minimizeTask(any(), /* deskId= */ eq(0), eq(task)) + } + + @Test fun onDesktopWindowMinimize_triesToStopTiling() { val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val transition = Binder() @@ -3972,7 +3999,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + taskRepository.addClosingTask( + displayId = DEFAULT_DISPLAY, + deskId = 0, + taskId = task2.taskId, + ) val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) @@ -4083,6 +4114,36 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_closeTransition_onlyDesktopTask_deactivatesDesk() { + val task = setUpFreeformTask() + + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + verify(desksOrganizer).deactivateDesk(any(), /* deskId= */ eq(0)) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_closeTransition_onlyDesktopTask_addsDeactivatesDeskTransition() { + val transition = Binder() + val task = setUpFreeformTask() + + controller.handleRequest(transition, createTransition(task, type = TRANSIT_CLOSE)) + + verify(desksTransitionsObserver) + .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0)) + } + + @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() { val task1 = setUpFreeformTask() @@ -4115,7 +4176,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + taskRepository.addClosingTask( + displayId = DEFAULT_DISPLAY, + deskId = 0, + taskId = task2.taskId, + ) val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) @@ -6240,6 +6305,61 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() assertThat(taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)).isEqualTo(currentDeskCount + 1) } + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + ) + fun startLaunchTransition_desktopNotShowing_movesWallpaperToFront() { + val launchingTask = createFreeformTask() + val wct = WindowContainerTransaction() + wct.reorder(launchingTask.token, /* onTop= */ true) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + anyOrNull(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + + val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) + val launchingTaskReorderIndex = latestWct.indexOfReorder(launchingTask, toTop = true) + val wallpaperReorderIndex = latestWct.indexOfReorder(wallpaperToken, toTop = true) + assertThat(launchingTaskReorderIndex).isNotEqualTo(-1) + assertThat(wallpaperReorderIndex).isNotEqualTo(-1) + assertThat(launchingTaskReorderIndex).isGreaterThan(wallpaperReorderIndex) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + ) + fun startLaunchTransition_desktopShowing_doesNotReorderWallpaper() { + val wct = WindowContainerTransaction() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + anyOrNull(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + setUpFreeformTask() + controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + + val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) + assertNull(latestWct.hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() }) + } + private class RunOnStartTransitionCallback : ((IBinder) -> Unit) { var invocations = 0 private set @@ -6626,13 +6746,20 @@ private fun WindowContainerTransaction.assertWithoutHop( } private fun WindowContainerTransaction.indexOfReorder( - task: RunningTaskInfo, + token: WindowContainerToken, toTop: Boolean? = null, ): Int { - val hop = hierarchyOps.singleOrNull(ReorderPredicate(task.token, toTop)) ?: return -1 + val hop = hierarchyOps.singleOrNull(ReorderPredicate(token, toTop)) ?: return -1 return hierarchyOps.indexOf(hop) } +private fun WindowContainerTransaction.indexOfReorder( + task: RunningTaskInfo, + toTop: Boolean? = null, +): Int { + return indexOfReorder(task.token, toTop) +} + private class ReorderPredicate(val token: WindowContainerToken, val toTop: Boolean? = null) : ((WindowContainerTransaction.HierarchyOp) -> Boolean) { override fun invoke(hop: WindowContainerTransaction.HierarchyOp): Boolean = @@ -6750,6 +6877,17 @@ private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: assertThat(op.pendingIntent?.intent?.categories).isEqualTo(intent.categories) } +private fun WindowContainerTransaction.assertLaunchTask(taskId: Int, windowingMode: Int) { + val keyLaunchWindowingMode = "android.activity.windowingMode" + + assertHop { hop -> + hop.type == HIERARCHY_OP_TYPE_LAUNCH_TASK && + hop.launchOptions?.getInt(LAUNCH_KEY_TASK_ID) == taskId && + hop.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED) == + windowingMode + } +} + private fun WindowContainerTransaction.assertLaunchTaskAt( index: Int, taskId: Int, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index d33209dbc30e..62e3c544e390 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -37,7 +37,6 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.StaticMockitoSession -import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION import com.android.wm.shell.ShellTaskOrganizer @@ -72,8 +71,6 @@ import org.mockito.Mockito.any import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.`when` -import org.mockito.kotlin.eq -import org.mockito.kotlin.verify import org.mockito.quality.Strictness /** @@ -521,85 +518,6 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test - fun minimizeTransitionReadyAndFinished_logsJankInstrumentationBeginAndEnd() { - desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } - val transition = Binder() - val task = setUpFreeformTask() - addPendingMinimizeChange(transition, taskId = task.taskId) - - callOnTransitionReady( - transition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - ) - - desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) - - verify(interactionJankMonitor) - .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - - desktopTasksLimiter - .getTransitionObserver() - .onTransitionFinished(transition, /* aborted= */ false) - - verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - } - - @Test - fun minimizeTransitionReadyAndAborted_logsJankInstrumentationBeginAndCancel() { - desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } - val transition = Binder() - val task = setUpFreeformTask() - addPendingMinimizeChange(transition, taskId = task.taskId) - - callOnTransitionReady( - transition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - ) - - desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) - - verify(interactionJankMonitor) - .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - - desktopTasksLimiter - .getTransitionObserver() - .onTransitionFinished(transition, /* aborted= */ true) - - verify(interactionJankMonitor).cancel(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - } - - @Test - fun minimizeTransitionReadyAndMerged_logsJankInstrumentationBeginAndEnd() { - desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } - val mergedTransition = Binder() - val newTransition = Binder() - val task = setUpFreeformTask() - addPendingMinimizeChange(mergedTransition, taskId = task.taskId) - - callOnTransitionReady( - mergedTransition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - ) - - desktopTasksLimiter.getTransitionObserver().onTransitionStarting(mergedTransition) - - verify(interactionJankMonitor) - .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - - desktopTasksLimiter - .getTransitionObserver() - .onTransitionMerged(mergedTransition, newTransition) - - verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - } - - @Test fun getMinimizingTask_noPendingTransition_returnsNull() { val transition = Binder() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt index 8b10ca1a2a70..96b85ad2729e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt @@ -22,6 +22,7 @@ import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.TransitionInfo import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.Change import android.window.WindowContainerTransaction.HierarchyOp import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT import androidx.test.filters.SmallTest @@ -29,15 +30,19 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask +import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer.DeskMinimizationRoot import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer.DeskRoot import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellInit import com.google.common.truth.Truth.assertThat +import kotlin.test.assertNotNull import org.junit.Assert.assertEquals import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.verify +import org.mockito.kotlin.argThat import org.mockito.kotlin.mock /** @@ -75,6 +80,43 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test + fun testCreateDesk_createsMinimizationRoot() { + val callback = FakeOnCreateCallback() + organizer.createDesk(Display.DEFAULT_DISPLAY, callback) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + + val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + + val minimizationRoot = organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId] + assertNotNull(minimizationRoot) + assertThat(minimizationRoot.deskId).isEqualTo(freeformRoot.taskId) + assertThat(minimizationRoot.rootId).isEqualTo(minimizationRootTask.taskId) + } + + @Test + fun testCreateMinimizationRoot_marksHidden() { + organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + + val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + + verify(mockShellTaskOrganizer) + .applyTransaction( + argThat { wct -> + wct.changes.any { change -> + change.key == minimizationRootTask.token.asBinder() && + (change.value.changeMask and Change.CHANGE_HIDDEN != 0) && + change.value.hidden + } + } + ) + } + + @Test fun testOnTaskAppeared_withoutRequest_throws() { val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } @@ -105,57 +147,122 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test + fun testOnTaskAppeared_duplicateMinimizedRoot_throws() { + organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + + assertThrows(Exception::class.java) { + organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + } + } + + @Test fun testOnTaskVanished_removesRoot() { val desk = createDesk() - organizer.onTaskVanished(desk.taskInfo) + organizer.onTaskVanished(desk.deskRoot.taskInfo) + + assertThat(organizer.deskRootsByDeskId.contains(desk.deskRoot.deskId)).isFalse() + } + + @Test + fun testOnTaskVanished_removesMinimizedRoot() { + val desk = createDesk() + + organizer.onTaskVanished(desk.deskRoot.taskInfo) + organizer.onTaskVanished(desk.minimizationRoot.taskInfo) - assertThat(organizer.roots.contains(desk.deskId)).isFalse() + assertThat(organizer.deskMinimizationRootsByDeskId.contains(desk.deskRoot.deskId)).isFalse() } @Test fun testDesktopWindowAppearsInDesk() { val desk = createDesk() - val child = createFreeformTask().apply { parentTaskId = desk.deskId } + val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } organizer.onTaskAppeared(child, SurfaceControl()) - assertThat(desk.children).contains(child.taskId) + assertThat(desk.deskRoot.children).contains(child.taskId) + } + + @Test + fun testDesktopWindowAppearsInDeskMinimizationRoot() { + val desk = createDesk() + val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } + + organizer.onTaskAppeared(child, SurfaceControl()) + + assertThat(desk.minimizationRoot.children).contains(child.taskId) + } + + @Test + fun testDesktopWindowMovesToMinimizationRoot() { + val desk = createDesk() + val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + organizer.onTaskAppeared(child, SurfaceControl()) + + child.parentTaskId = desk.minimizationRoot.rootId + organizer.onTaskInfoChanged(child) + + assertThat(desk.deskRoot.children).doesNotContain(child.taskId) + assertThat(desk.minimizationRoot.children).contains(child.taskId) } @Test fun testDesktopWindowDisappearsFromDesk() { val desk = createDesk() - val child = createFreeformTask().apply { parentTaskId = desk.deskId } + val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } organizer.onTaskAppeared(child, SurfaceControl()) organizer.onTaskVanished(child) - assertThat(desk.children).doesNotContain(child.taskId) + assertThat(desk.deskRoot.children).doesNotContain(child.taskId) } @Test - fun testRemoveDesk() { + fun testDesktopWindowDisappearsFromDeskMinimizationRoot() { + val desk = createDesk() + val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } + + organizer.onTaskAppeared(child, SurfaceControl()) + organizer.onTaskVanished(child) + + assertThat(desk.minimizationRoot.children).doesNotContain(child.taskId) + } + + @Test + fun testRemoveDesk_removesDeskRoot() { val desk = createDesk() val wct = WindowContainerTransaction() - organizer.removeDesk(wct, desk.deskId) + organizer.removeDesk(wct, desk.deskRoot.deskId) assertThat( wct.hierarchyOps.any { hop -> hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK && - hop.container == desk.taskInfo.token.asBinder() + hop.container == desk.deskRoot.token.asBinder() } ) .isTrue() } @Test - fun testRemoveDesk_didNotExist_throws() { - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + fun testRemoveDesk_removesMinimizationRoot() { + val desk = createDesk() val wct = WindowContainerTransaction() - assertThrows(Exception::class.java) { organizer.removeDesk(wct, freeformRoot.taskId) } + organizer.removeDesk(wct, desk.deskRoot.deskId) + + assertThat( + wct.hierarchyOps.any { hop -> + hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK && + hop.container == desk.minimizationRoot.token.asBinder() + } + ) + .isTrue() } @Test @@ -163,20 +270,20 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { val desk = createDesk() val wct = WindowContainerTransaction() - organizer.activateDesk(wct, desk.deskId) + organizer.activateDesk(wct, desk.deskRoot.deskId) assertThat( wct.hierarchyOps.any { hop -> hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REORDER && hop.toTop && - hop.container == desk.taskInfo.token.asBinder() + hop.container == desk.deskRoot.taskInfo.token.asBinder() } ) .isTrue() assertThat( wct.hierarchyOps.any { hop -> hop.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT && - hop.container == desk.taskInfo.token.asBinder() + hop.container == desk.deskRoot.taskInfo.token.asBinder() } ) .isTrue() @@ -196,14 +303,14 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { val desktopTask = createFreeformTask().apply { parentTaskId = -1 } val wct = WindowContainerTransaction() - organizer.moveTaskToDesk(wct, desk.deskId, desktopTask) + organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, desktopTask) assertThat( wct.hierarchyOps.any { hop -> hop.isReparent && hop.toTop && hop.container == desktopTask.token.asBinder() && - hop.newParent == desk.taskInfo.token.asBinder() + hop.newParent == desk.deskRoot.taskInfo.token.asBinder() } ) .isTrue() @@ -231,13 +338,26 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { fun testGetDeskAtEnd() { val desk = createDesk() - val task = createFreeformTask().apply { parentTaskId = desk.deskId } + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val endDesk = + organizer.getDeskAtEnd( + TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task } + ) + + assertThat(endDesk).isEqualTo(desk.deskRoot.deskId) + } + + @Test + fun testGetDeskAtEnd_inMinimizationRoot() { + val desk = createDesk() + + val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } val endDesk = organizer.getDeskAtEnd( TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task } ) - assertThat(endDesk).isEqualTo(desk.deskId) + assertThat(endDesk).isEqualTo(desk.deskRoot.deskId) } @Test @@ -264,14 +384,14 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { fun deactivateDesk_clearsLaunchRoot() { val wct = WindowContainerTransaction() val desk = createDesk() - organizer.activateDesk(wct, desk.deskId) + organizer.activateDesk(wct, desk.deskRoot.deskId) - organizer.deactivateDesk(wct, desk.deskId) + organizer.deactivateDesk(wct, desk.deskRoot.deskId) assertThat( wct.hierarchyOps.any { hop -> hop.type == HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT && - hop.container == desk.taskInfo.token.asBinder() && + hop.container == desk.deskRoot.taskInfo.token.asBinder() && hop.windowingModes == null && hop.activityTypes == null } @@ -280,25 +400,129 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun isDeskChange() { + fun isDeskChange_forDeskId() { val desk = createDesk() assertThat( organizer.isDeskChange( - TransitionInfo.Change(desk.taskInfo.token, desk.leash).apply { - taskInfo = desk.taskInfo + TransitionInfo.Change(desk.deskRoot.taskInfo.token, desk.deskRoot.leash).apply { + taskInfo = desk.deskRoot.taskInfo }, - desk.deskId, + desk.deskRoot.deskId, + ) + ) + .isTrue() + } + + @Test + fun isDeskChange_forDeskId_inMinimizationRoot() { + val desk = createDesk() + + assertThat( + organizer.isDeskChange( + change = + TransitionInfo.Change( + desk.minimizationRoot.token, + desk.minimizationRoot.leash, + ) + .apply { taskInfo = desk.minimizationRoot.taskInfo }, + deskId = desk.deskRoot.deskId, + ) + ) + .isTrue() + } + + @Test + fun isDeskChange_anyDesk() { + val desk = createDesk() + + assertThat( + organizer.isDeskChange( + change = + TransitionInfo.Change(desk.deskRoot.taskInfo.token, desk.deskRoot.leash) + .apply { taskInfo = desk.deskRoot.taskInfo } + ) + ) + .isTrue() + } + + @Test + fun isDeskChange_anyDesk_inMinimizationRoot() { + val desk = createDesk() + + assertThat( + organizer.isDeskChange( + change = + TransitionInfo.Change( + desk.minimizationRoot.taskInfo.token, + desk.minimizationRoot.leash, + ) + .apply { taskInfo = desk.minimizationRoot.taskInfo } ) ) .isTrue() } - private fun createDesk(): DeskRoot { + @Test + fun minimizeTask() { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task) + organizer.onTaskAppeared(task, SurfaceControl()) + + organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task) + + assertThat( + wct.hierarchyOps.any { hop -> + hop.isReparent && + hop.container == task.token.asBinder() && + hop.newParent == desk.minimizationRoot.token.asBinder() + } + ) + .isTrue() + } + + @Test + fun minimizeTask_alreadyMinimized_noOp() { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } + val wct = WindowContainerTransaction() + organizer.onTaskAppeared(task, SurfaceControl()) + + organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task) + + assertThat(wct.isEmpty).isTrue() + } + + @Test + fun minimizeTask_inDifferentDesk_noOp() { + val desk = createDesk() + val otherDesk = createDesk() + val task = createFreeformTask().apply { parentTaskId = otherDesk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.onTaskAppeared(task, SurfaceControl()) + + organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task) + + assertThat(wct.isEmpty).isTrue() + } + + private data class DeskRoots( + val deskRoot: DeskRoot, + val minimizationRoot: DeskMinimizationRoot, + ) + + private fun createDesk(): DeskRoots { organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - return organizer.roots[freeformRoot.taskId] + val minimizationRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(minimizationRoot, SurfaceControl()) + return DeskRoots( + organizer.deskRootsByDeskId[freeformRoot.taskId], + checkNotNull(organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId]), + ) } private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java index fd5e567f69ed..93dd3456f3f2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java @@ -17,16 +17,20 @@ package com.android.wm.shell.recents; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX; +import static com.android.wm.shell.Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION; import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING; import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING; import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED; +import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION; import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION; import static com.google.common.truth.Truth.assertThat; @@ -64,7 +68,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.os.IResultReceiver; -import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; @@ -73,6 +76,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopUserRepositories; +import com.android.wm.shell.shared.R; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -308,8 +312,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { mRecentsTransitionHandler.findController(transition).merge( mergeTransitionInfo, new StubTransaction(), - finishT, - transition, + new StubTransaction(), mock(Transitions.TransitionFinishCallback.class)); mMainExecutor.flushAll(); @@ -318,6 +321,69 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { } @Test + @EnableFlags(FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION) + public void testMerge_consumeBookendTransition() throws Exception { + // Start and finish the transition + final IRecentsAnimationRunner animationRunner = mock(IRecentsAnimationRunner.class); + final IBinder transition = startRecentsTransition(/* synthetic= */ false, animationRunner); + mRecentsTransitionHandler.startAnimation( + transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(), + mock(Transitions.TransitionFinishCallback.class)); + mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false, + false /* sendUserLeaveHint */, mock(IResultReceiver.class)); + mMainExecutor.flushAll(); + + // Merge the bookend transition + TransitionInfo mergeTransitionInfo = + new TransitionInfoBuilder(TRANSIT_END_RECENTS_TRANSITION) + .addChange(TRANSIT_OPEN, new TestRunningTaskInfoBuilder().build()) + .build(); + SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + Transitions.TransitionFinishCallback finishCallback + = mock(Transitions.TransitionFinishCallback.class); + mRecentsTransitionHandler.findController(transition).merge( + mergeTransitionInfo, + new StubTransaction(), + finishT, + finishCallback); + mMainExecutor.flushAll(); + + // Verify that we've merged + verify(finishCallback).onTransitionFinished(any()); + } + + @Test + @EnableFlags(FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION) + public void testMerge_pendingBookendTransition_mergesTransition() throws Exception { + // Start and finish the transition + final IRecentsAnimationRunner animationRunner = mock(IRecentsAnimationRunner.class); + final IBinder transition = startRecentsTransition(/* synthetic= */ false, animationRunner); + mRecentsTransitionHandler.startAnimation( + transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(), + mock(Transitions.TransitionFinishCallback.class)); + mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false, + false /* sendUserLeaveHint */, mock(IResultReceiver.class)); + mMainExecutor.flushAll(); + + // Merge a new transition while we have a pending finish + TransitionInfo mergeTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN, new TestRunningTaskInfoBuilder().build()) + .build(); + SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + Transitions.TransitionFinishCallback finishCallback + = mock(Transitions.TransitionFinishCallback.class); + mRecentsTransitionHandler.findController(transition).merge( + mergeTransitionInfo, + new StubTransaction(), + finishT, + finishCallback); + mMainExecutor.flushAll(); + + // Verify that we've cleaned up the original transition + assertNull(mRecentsTransitionHandler.findController(transition)); + } + + @Test @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX) public void testMergeAndFinish_openingFreeformTasks_setsCornerRadius() { ActivityManager.RunningTaskInfo freeformTask = @@ -336,7 +402,6 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { mergeTransitionInfo, new StubTransaction(), finishT, - transition, mock(Transitions.TransitionFinishCallback.class)); mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false, false /* sendUserLeaveHint */, mock(IResultReceiver.class)); @@ -385,15 +450,23 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { } private TransitionInfo createTransitionInfo() { - final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder() + final ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() .setTopActivityType(ACTIVITY_TYPE_HOME) .build(); + final ActivityManager.RunningTaskInfo appTask = new TestRunningTaskInfoBuilder() + .setTopActivityType(ACTIVITY_TYPE_STANDARD) + .build(); final TransitionInfo.Change homeChange = new TransitionInfo.Change( - task.token, new SurfaceControl()); + homeTask.token, new SurfaceControl()); homeChange.setMode(TRANSIT_TO_FRONT); - homeChange.setTaskInfo(task); + homeChange.setTaskInfo(homeTask); + final TransitionInfo.Change appChange = new TransitionInfo.Change( + appTask.token, new SurfaceControl()); + appChange.setMode(TRANSIT_TO_FRONT); + appChange.setTaskInfo(appTask); return new TransitionInfoBuilder(TRANSIT_START_RECENTS_TRANSITION) .addChange(homeChange) + .addChange(appChange) .build(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/MinimizeAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/MinimizeAnimatorTest.kt new file mode 100644 index 000000000000..ba609d4322fc --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/MinimizeAnimatorTest.kt @@ -0,0 +1,118 @@ +/* + * 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.wm.shell.shared.animation + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.content.Context +import android.content.res.Resources +import android.os.Handler +import android.util.DisplayMetrics +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction +import android.window.TransitionInfo +import android.window.WindowContainerToken +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW +import com.android.internal.jank.InteractionJankMonitor +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class MinimizeAnimatorTest { + private val context = mock<Context>() + private val resources = mock<Resources>() + private val transaction = mock<Transaction>() + private val leash = mock<SurfaceControl>() + private val interactionJankMonitor = mock<InteractionJankMonitor>() + private val animationHandler = mock<Handler>() + + private val displayMetrics = DisplayMetrics().apply { density = 1f } + + @Before + fun setup() { + whenever(context.resources).thenReturn(resources) + whenever(resources.displayMetrics).thenReturn(displayMetrics) + whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction) + whenever(transaction.setPosition(any(), anyFloat(), anyFloat())).thenReturn(transaction) + whenever(transaction.setScale(any(), anyFloat(), anyFloat())).thenReturn(transaction) + whenever(transaction.setFrameTimeline(anyLong())).thenReturn(transaction) + } + + @Test + fun create_returnsBoundsAndAlphaAnimators() { + val change = TransitionInfo.Change(mock<WindowContainerToken>(), leash) + + val animator = createAnimator(change) + + assertThat(animator).isInstanceOf(AnimatorSet::class.java) + val animatorSet = animator as AnimatorSet + assertThat(animatorSet.childAnimations).hasSize(2) + assertIsBoundsAnimator(animatorSet.childAnimations[0]) + assertIsAlphaAnimator(animatorSet.childAnimations[1]) + } + + @Test + fun create_doesNotlogJankInstrumentation() = runOnUiThread { + val change = TransitionInfo.Change(mock<WindowContainerToken>(), leash) + + createAnimator(change) + + verify(interactionJankMonitor, never()).begin( + leash, context, animationHandler, CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) + } + + @Test + fun onAnimationStart_logsJankInstrumentation() = runOnUiThread { + val change = TransitionInfo.Change(mock<WindowContainerToken>(), leash) + + createAnimator(change).start() + + verify(interactionJankMonitor).begin( + leash, context, animationHandler, CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) + } + + private fun createAnimator(change: TransitionInfo.Change): Animator = + MinimizeAnimator.create(context, change, transaction, {}, interactionJankMonitor, + animationHandler) + + private fun assertIsBoundsAnimator(animator: Animator) { + assertThat(animator).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.duration).isEqualTo(200) + assertThat(animator.interpolator).isEqualTo(Interpolators.STANDARD_ACCELERATE) + } + + private fun assertIsAlphaAnimator(animator: Animator) { + assertThat(animator).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.duration).isEqualTo(100) + assertThat(animator.interpolator).isEqualTo(Interpolators.LINEAR) + } +} + diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt index f69bf34ea3f7..88c6e499b869 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt @@ -16,13 +16,16 @@ package com.android.wm.shell.shared.desktopmode +import android.Manifest.permission.SYSTEM_ALERT_WINDOW import android.app.TaskInfo import android.compat.testing.PlatformCompatChangeRule import android.content.ComponentName import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.os.Process +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest @@ -39,7 +42,9 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString import org.mockito.kotlin.any +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -55,6 +60,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy private val packageManager: PackageManager = mock() private val homeActivities = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "") + private val baseActivityTest = ComponentName("com.test.dummypackage", "TestClass") @Before fun setUp() { @@ -64,6 +70,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() { assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) @@ -71,10 +78,39 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { isActivityStackTransparent = true isTopActivityNoDisplay = false numActivities = 1 + baseActivity = baseActivityTest })) } @Test + @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) + fun testIsTopActivityExemptWithPermission_onlyTransparentActivitiesInStack() { + allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW)) + assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( + createFreeformTask(/* displayId */ 0) + .apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = false + numActivities = 1 + baseActivity = baseActivityTest + })) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) + fun testIsTopActivityExemptWithNoPermission_onlyTransparentActivitiesInStack() { + allowOverlayPermission(arrayOf()) + assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( + createFreeformTask(/* displayId */ 0) + .apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = false + numActivities = 1 + baseActivity = baseActivityTest + })) + } + + @Test fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() { assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) @@ -219,4 +255,15 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { } } } + + fun allowOverlayPermission(permissions: Array<String>) { + val packageInfo = mock<PackageInfo>() + packageInfo.requestedPermissions = permissions + whenever( + packageManager.getPackageInfo( + anyString(), + eq(PackageManager.GET_PERMISSIONS) + ) + ).thenReturn(packageInfo) + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java index 6f28e656d060..3099b0f5cf66 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java @@ -17,36 +17,44 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.WindowConfiguration.ActivityType; import android.content.Context; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionInfo.TransitionMode; +import android.window.WindowContainerToken; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; @@ -188,6 +196,72 @@ public class HomeTransitionObserverTest extends ShellTestCase { } @Test + @EnableFlags({Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE}) + public void testDragTaskToBubbleOverHome_notifiesHomeIsVisible() throws RemoteException { + ActivityManager.RunningTaskInfo homeTask = createTaskInfo(1, ACTIVITY_TYPE_HOME); + ActivityManager.RunningTaskInfo bubbleTask = createTaskInfo(2, ACTIVITY_TYPE_STANDARD); + + TransitionInfo startDragTransition = + new TransitionInfoBuilder(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) + .addChange(TRANSIT_TO_FRONT, homeTask) + .addChange(TRANSIT_TO_BACK, bubbleTask) + .build(); + + // Start drag to desktop which brings home to front + mHomeTransitionObserver.onTransitionReady(new Binder(), startDragTransition, + MockTransactionPool.create(), MockTransactionPool.create()); + // Does not notify home visibility yet + verify(mListener, never()).onHomeVisibilityChanged(anyBoolean()); + + TransitionInfo convertToBubbleTransition = + new TransitionInfoBuilder(TRANSIT_CONVERT_TO_BUBBLE) + .addChange(TRANSIT_TO_FRONT, bubbleTask) + .build(); + + // Convert to bubble. Transition does not include changes for home task + mHomeTransitionObserver.onTransitionReady(new Binder(), convertToBubbleTransition, + MockTransactionPool.create(), MockTransactionPool.create()); + + // Notifies home visibility change that was pending from the start of drag + verify(mListener).onHomeVisibilityChanged(true); + } + + @Test + @EnableFlags({Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE}) + public void testDragTaskToBubbleOverOtherTask_notifiesHomeIsNotVisible() + throws RemoteException { + ActivityManager.RunningTaskInfo homeTask = createTaskInfo(1, ACTIVITY_TYPE_HOME); + ActivityManager.RunningTaskInfo bubbleTask = createTaskInfo(2, ACTIVITY_TYPE_STANDARD); + ActivityManager.RunningTaskInfo otherTask = createTaskInfo(3, ACTIVITY_TYPE_STANDARD); + + TransitionInfo startDragTransition = + new TransitionInfoBuilder(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) + .addChange(TRANSIT_TO_FRONT, homeTask) + .addChange(TRANSIT_TO_BACK, bubbleTask) + .build(); + + // Start drag to desktop which brings home to front + mHomeTransitionObserver.onTransitionReady(new Binder(), startDragTransition, + MockTransactionPool.create(), MockTransactionPool.create()); + // Does not notify home visibility yet + verify(mListener, never()).onHomeVisibilityChanged(anyBoolean()); + + TransitionInfo convertToBubbleTransition = + new TransitionInfoBuilder(TRANSIT_CONVERT_TO_BUBBLE) + .addChange(TRANSIT_TO_FRONT, bubbleTask) + .addChange(TRANSIT_TO_FRONT, otherTask) + .addChange(TRANSIT_TO_BACK, homeTask) + .build(); + + // Convert to bubble. Transition includes home task to back which updates home visibility + mHomeTransitionObserver.onTransitionReady(new Binder(), convertToBubbleTransition, + MockTransactionPool.create(), MockTransactionPool.create()); + + // Notifies home visibility change due to home moving to back in the second transition + verify(mListener).onHomeVisibilityChanged(false); + } + + @Test public void testHomeActivityWithBackGestureNotifiesHomeIsVisibleAfterClose() throws RemoteException { TransitionInfo info = mock(TransitionInfo.class); @@ -227,4 +301,14 @@ public class HomeTransitionObserverTest extends ShellTestCase { when(change.getMode()).thenReturn(mode); taskInfo.isRunning = isRunning; } + + private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int activityType) { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.topActivityType = activityType; + taskInfo.configuration.windowConfiguration.setActivityType(activityType); + taskInfo.token = mock(WindowContainerToken.class); + taskInfo.isRunning = true; + return taskInfo; + } } 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 8cccdb2b6120..81dfaed56b6f 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 @@ -52,6 +52,7 @@ import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler @@ -138,6 +139,8 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { protected val mockFreeformTaskTransitionStarter = mock<FreeformTaskTransitionStarter>() protected val mockActivityOrientationChangeHandler = mock<DesktopActivityOrientationChangeHandler>() + protected val mockMultiDisplayDragMoveIndicatorController = + mock<MultiDisplayDragMoveIndicatorController>() protected val mockInputManager = mock<InputManager>() private val mockTaskPositionerFactory = mock<DesktopModeWindowDecorViewModel.TaskPositionerFactory>() @@ -229,6 +232,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { mockRecentsTransitionHandler, desktopModeCompatPolicy, mockTilingWindowDecoration, + mockMultiDisplayDragMoveIndicatorController, ) desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) @@ -243,6 +247,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { any(), any(), any(), + any(), any() ) ) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt index 937938df82c8..a6b077037b86 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt @@ -41,6 +41,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.MultiDisplayDragMoveIndicatorController import com.android.wm.shell.common.MultiDisplayTestUtil import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionFinishCallback @@ -62,8 +63,8 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.`when` +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations /** @@ -93,7 +94,8 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { @Mock private lateinit var mockTransitions: Transitions @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor @Mock private lateinit var mockSurfaceControl: SurfaceControl - + @Mock private lateinit var mockMultiDisplayDragMoveIndicatorController: + MultiDisplayDragMoveIndicatorController private lateinit var resources: TestableResources private lateinit var spyDisplayLayout0: DisplayLayout private lateinit var spyDisplayLayout1: DisplayLayout @@ -170,10 +172,11 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { mockDesktopWindowDecoration, mockDisplayController, mockDragEventListener, - mockTransactionFactory, + { mockTransaction }, mockTransitions, mockInteractionJankMonitor, mainHandler, + mockMultiDisplayDragMoveIndicatorController, ) } diff --git a/libs/hostgraphics/include/gui/BufferItemConsumer.h b/libs/hostgraphics/include/gui/BufferItemConsumer.h index c25941151800..5c96c82e061c 100644 --- a/libs/hostgraphics/include/gui/BufferItemConsumer.h +++ b/libs/hostgraphics/include/gui/BufferItemConsumer.h @@ -17,6 +17,8 @@ #ifndef ANDROID_GUI_BUFFERITEMCONSUMER_H #define ANDROID_GUI_BUFFERITEMCONSUMER_H +#include <com_android_graphics_libgui_flags.h> +#include <gui/BufferQueue.h> #include <gui/ConsumerBase.h> #include <gui/IGraphicBufferConsumer.h> #include <utils/RefBase.h> @@ -26,9 +28,22 @@ namespace android { class BufferItemConsumer : public ConsumerBase { public: BufferItemConsumer(const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage, - int bufferCount, bool controlledByApp) + int bufferCount = -1, bool controlledByApp = false) : mConsumer(consumer) {} +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) + BufferItemConsumer(uint64_t consumerUsage, int bufferCount = -1, + bool controlledByApp = false, bool isConsumerSurfaceFlinger = false) { + sp<IGraphicBufferProducer> producer; + BufferQueue::createBufferQueue(&producer, &mConsumer); + mSurface = sp<Surface>::make(producer, controlledByApp); + } + + status_t setConsumerIsProtected(bool isProtected) { + return OK; + } +#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) + status_t acquireBuffer(BufferItem* item, nsecs_t presentWhen, bool waitForFence = true) { return mConsumer->acquireBuffer(item, presentWhen, 0); } @@ -71,8 +86,20 @@ public: return OK; } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) +// Returns a Surface that can be used as the producer for this consumer. + sp<Surface> getSurface() const { + return mSurface; + } +#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) + private: sp<IGraphicBufferConsumer> mConsumer; +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) + // This Surface wraps the IGraphicBufferConsumer created for this + // ConsumerBase. + sp<Surface> mSurface; +#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) }; } // namespace android diff --git a/libs/hostgraphics/include/gui/Surface.h b/libs/hostgraphics/include/gui/Surface.h index 2774f89cb54c..e268ce6ca769 100644 --- a/libs/hostgraphics/include/gui/Surface.h +++ b/libs/hostgraphics/include/gui/Surface.h @@ -34,6 +34,10 @@ public: ANativeWindow::query = hook_query; } + sp<IGraphicBufferProducer> getIGraphicBufferProducer() const { + return mBufferProducer; + } + static bool isValid(const sp<Surface>& surface) { return surface != nullptr; } diff --git a/libs/hostgraphics/include/ui/Fence.h b/libs/hostgraphics/include/ui/Fence.h index 187c3116f61c..3364b8aed605 100644 --- a/libs/hostgraphics/include/ui/Fence.h +++ b/libs/hostgraphics/include/ui/Fence.h @@ -60,6 +60,10 @@ public: return 0; } + int get() const { + return 0; + } + inline Status getStatus() { // The sync_wait call underlying wait() has been measured to be // significantly faster than the sync_fence_info call underlying diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index bb2a53bc04d6..38ac8ab7135e 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -233,6 +233,14 @@ java_sdk_library { } filegroup { + name: "framework-graphics-ravenwood-policies", + srcs: [ + "framework-graphics-ravenwood-policies.txt", + ], + visibility: ["//frameworks/base/ravenwood"], +} + +filegroup { name: "framework-graphics-srcs", srcs: [ "apex/java/**/*.java", @@ -461,6 +469,10 @@ cc_defaults { }, linux: { srcs: ["platform/linux/utils/SharedLib.cpp"], + shared_libs: [ + "libbinder", + "libbinder_ndk", + ], }, darwin: { srcs: ["platform/darwin/utils/SharedLib.cpp"], diff --git a/libs/hwui/framework-graphics-ravenwood-policies.txt b/libs/hwui/framework-graphics-ravenwood-policies.txt new file mode 100644 index 000000000000..7296225ccfe8 --- /dev/null +++ b/libs/hwui/framework-graphics-ravenwood-policies.txt @@ -0,0 +1 @@ +class android.graphics.ColorMatrix keepclass diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index cfde0b28c0d5..27d4ac7cef4b 100644 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -613,7 +613,7 @@ static void Bitmap_setHasMipMap(JNIEnv* env, jobject, jlong bitmapHandle, /////////////////////////////////////////////////////////////////////////////// // TODO: Move somewhere else -#ifdef __ANDROID__ // Layoutlib does not support parcel +#ifdef __linux__ // Only Linux support parcel #define ON_ERROR_RETURN(X) \ if ((error = (X)) != STATUS_OK) return error @@ -717,7 +717,7 @@ static binder_status_t writeBlob(AParcel* parcel, uint64_t bitmapId, const SkBit #undef ON_ERROR_RETURN -#endif // __ANDROID__ // Layoutlib does not support parcel +#endif // __linux__ // Only Linux support parcel // This is the maximum possible size because the SkColorSpace must be // representable (and therefore serializable) using a matrix and numerical @@ -733,7 +733,7 @@ static bool validateImageInfo(const SkImageInfo& info, int32_t rowBytes) { } static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { -#ifdef __ANDROID__ // Layoutlib does not support parcel +#ifdef __linux__ // Only Linux support parcel if (parcel == NULL) { jniThrowNullPointerException(env, "parcel cannot be null"); return NULL; @@ -836,14 +836,14 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable), nullptr, nullptr, density, sourceId); #else - jniThrowRuntimeException(env, "Cannot use parcels outside of Android"); + jniThrowRuntimeException(env, "Cannot use parcels outside of Linux"); return NULL; #endif } static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jint density, jobject parcel) { -#ifdef __ANDROID__ // Layoutlib does not support parcel +#ifdef __linux__ // Only Linux support parcel if (parcel == NULL) { ALOGD("------- writeToParcel null parcel\n"); return JNI_FALSE; @@ -901,7 +901,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, j } return JNI_TRUE; #else - doThrowRE(env, "Cannot use parcels outside of Android"); + doThrowRE(env, "Cannot use parcels outside of Linux"); return JNI_FALSE; #endif } diff --git a/libs/hwui/jni/Region.cpp b/libs/hwui/jni/Region.cpp index 1e064b820591..76986eeb079d 100644 --- a/libs/hwui/jni/Region.cpp +++ b/libs/hwui/jni/Region.cpp @@ -18,7 +18,7 @@ #include "SkPath.h" #include "GraphicsJNI.h" -#ifdef __ANDROID__ // Layoutlib does not support parcel +#ifdef __linux__ // Only Linux support parcel #include <android/binder_parcel.h> #include <android/binder_parcel_jni.h> #include <android/binder_parcel_utils.h> @@ -202,7 +202,7 @@ static jstring Region_toString(JNIEnv* env, jobject clazz, jlong regionHandle) { static jlong Region_createFromParcel(JNIEnv* env, jobject clazz, jobject parcel) { -#ifdef __ANDROID__ // Layoutlib does not support parcel +#ifdef __linux__ // Only Linux support parcel if (parcel == nullptr) { return 0; } @@ -230,7 +230,7 @@ static jlong Region_createFromParcel(JNIEnv* env, jobject clazz, jobject parcel) static jboolean Region_writeToParcel(JNIEnv* env, jobject clazz, jlong regionHandle, jobject parcel) { -#ifdef __ANDROID__ // Layoutlib does not support parcel +#ifdef __linux__ // Only Linux support parcel const SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); if (parcel == nullptr) { return JNI_FALSE; diff --git a/libs/hwui/jni/ScopedParcel.cpp b/libs/hwui/jni/ScopedParcel.cpp index 95e4e01d8df8..52cd988344b0 100644 --- a/libs/hwui/jni/ScopedParcel.cpp +++ b/libs/hwui/jni/ScopedParcel.cpp @@ -15,7 +15,7 @@ */ #include "ScopedParcel.h" -#ifdef __ANDROID__ // Layoutlib does not support parcel +#ifdef __linux__ // Only Linux support parcel using namespace android; @@ -92,4 +92,4 @@ void ScopedParcel::writeData(const std::optional<sk_sp<SkData>>& optData) { AParcel_writeByteArray(mParcel, nullptr, -1); } } -#endif // __ANDROID__ // Layoutlib does not support parcel +#endif // __linux__ // Only Linux support parcel diff --git a/libs/hwui/jni/ScopedParcel.h b/libs/hwui/jni/ScopedParcel.h index f2f138fda43c..f2b793a354d7 100644 --- a/libs/hwui/jni/ScopedParcel.h +++ b/libs/hwui/jni/ScopedParcel.h @@ -15,7 +15,7 @@ */ #include "SkData.h" -#ifdef __ANDROID__ // Layoutlib does not support parcel +#ifdef __linux__ // Only Linux support parcel #include <android-base/unique_fd.h> #include <android/binder_parcel.h> #include <android/binder_parcel_jni.h> @@ -64,4 +64,4 @@ enum class BlobType : int32_t { ASHMEM, }; -#endif // __ANDROID__ // Layoutlib does not support parcel
\ No newline at end of file +#endif // __linux__ // Only Linux support parcel diff --git a/libs/hwui/jni/graphics_jni_helpers.h b/libs/hwui/jni/graphics_jni_helpers.h index 91db134af18f..ff26ec1771bd 100644 --- a/libs/hwui/jni/graphics_jni_helpers.h +++ b/libs/hwui/jni/graphics_jni_helpers.h @@ -21,6 +21,7 @@ #include <nativehelper/JNIPlatformHelp.h> #include <nativehelper/scoped_local_ref.h> #include <nativehelper/scoped_utf_chars.h> +#include <nativehelper/scoped_primitive_array.h> #include <string> // Host targets (layoutlib) do not differentiate between regular and critical native methods, diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java index 892a8612d74a..56d3df3b2555 100644 --- a/media/java/android/media/AudioDeviceVolumeManager.java +++ b/media/java/android/media/AudioDeviceVolumeManager.java @@ -16,6 +16,9 @@ package android.media; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.media.audio.Flags.FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT; + import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL; import android.Manifest; @@ -91,6 +94,8 @@ public class AudioDeviceVolumeManager { * @see #setDeviceAbsoluteVolumeBehavior(AudioDeviceAttributes, VolumeInfo, boolean, Executor, * OnAudioDeviceVolumeChangedListener) */ + @SystemApi(client = MODULE_LIBRARIES) + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) public interface OnAudioDeviceVolumeChangedListener { /** * Called the device for the given audio device has changed. @@ -203,6 +208,30 @@ public class AudioDeviceVolumeManager { * volume updates to apply on that device * @param device the audio device set to absolute volume mode * @param volume the type of volume this device responds to + * @param executor the Executor used for receiving volume updates through the listener + * @param vclistener the callback for volume updates + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, + android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + android.Manifest.permission.BLUETOOTH_STACK}) + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public void setDeviceAbsoluteVolumeBehavior( + @NonNull AudioDeviceAttributes device, + @NonNull VolumeInfo volume, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnAudioDeviceVolumeChangedListener vclistener) { + setDeviceAbsoluteVolumeBehavior(device, volume, /*handlesVolumeAdjustment=*/false, executor, + vclistener); + } + + /** + * @hide + * Configures a device to use absolute volume model, and registers a listener for receiving + * volume updates to apply on that device + * @param device the audio device set to absolute volume mode + * @param volume the type of volume this device responds to * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately * from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume} * will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}. @@ -210,7 +239,7 @@ public class AudioDeviceVolumeManager { * @param vclistener the callback for volume updates */ @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.BLUETOOTH_PRIVILEGED }) + android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void setDeviceAbsoluteVolumeBehavior( @NonNull AudioDeviceAttributes device, @NonNull VolumeInfo volume, @@ -229,6 +258,30 @@ public class AudioDeviceVolumeManager { * registers a listener for receiving volume updates to apply on that device * @param device the audio device set to absolute multi-volume mode * @param volumes the list of volumes the given device responds to + * @param executor the Executor used for receiving volume updates through the listener + * @param vclistener the callback for volume updates + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, + android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + android.Manifest.permission.BLUETOOTH_STACK}) + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public void setDeviceAbsoluteMultiVolumeBehavior( + @NonNull AudioDeviceAttributes device, + @NonNull List<VolumeInfo> volumes, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnAudioDeviceVolumeChangedListener vclistener) { + setDeviceAbsoluteMultiVolumeBehavior(device, volumes, /*handlesVolumeAdjustment=*/false, + executor, vclistener); + } + + /** + * @hide + * Configures a device to use absolute volume model applied to different volume types, and + * registers a listener for receiving volume updates to apply on that device + * @param device the audio device set to absolute multi-volume mode + * @param volumes the list of volumes the given device responds to * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately * from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume} * will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}. @@ -236,7 +289,7 @@ public class AudioDeviceVolumeManager { * @param vclistener the callback for volume updates */ @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.BLUETOOTH_PRIVILEGED }) + android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void setDeviceAbsoluteMultiVolumeBehavior( @NonNull AudioDeviceAttributes device, @NonNull List<VolumeInfo> volumes, diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 4fe0b80f3951..275972495206 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -715,47 +715,45 @@ void ASurfaceTransaction_setLuts(ASurfaceTransaction* aSurfaceTransaction, sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); - int fd = -1; + base::unique_fd fd; std::vector<int32_t> offsets; std::vector<int32_t> dimensions; std::vector<int32_t> sizes; std::vector<int32_t> samplingKeys; if (luts) { - std::vector<float> buffer(luts->totalBufferSize); int32_t count = luts->offsets.size(); offsets = luts->offsets; dimensions.reserve(count); sizes.reserve(count); samplingKeys.reserve(count); - for (int32_t i = 0; i < count; i++) { - dimensions.emplace_back(luts->entries[i]->properties.dimension); - sizes.emplace_back(luts->entries[i]->properties.size); - samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey); - std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(), - buffer.begin() + offsets[i]); - } // mmap - fd = ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float)); + fd.reset(ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float))); if (fd < 0) { LOG_ALWAYS_FATAL("setLuts, ashmem_create_region() failed"); return; } - void* ptr = mmap(nullptr, luts->totalBufferSize * sizeof(float), PROT_READ | PROT_WRITE, - MAP_SHARED, fd, 0); + float* ptr = (float*)mmap(nullptr, luts->totalBufferSize * sizeof(float), + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { LOG_ALWAYS_FATAL("setLuts, Failed to map the shared memory"); return; } - memcpy(ptr, buffer.data(), luts->totalBufferSize * sizeof(float)); + for (int32_t i = 0; i < count; i++) { + dimensions.emplace_back(luts->entries[i]->properties.dimension); + sizes.emplace_back(luts->entries[i]->properties.size); + samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey); + std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(), + ptr + offsets[i]); + } + munmap(ptr, luts->totalBufferSize * sizeof(float)); } - transaction->setLuts(surfaceControl, base::unique_fd(fd), offsets, dimensions, sizes, - samplingKeys); + transaction->setLuts(surfaceControl, std::move(fd), offsets, dimensions, sizes, samplingKeys); } void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction, diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp index bd892637a5eb..853d1ad0dc94 100644 --- a/packages/CtsShim/build/Android.bp +++ b/packages/CtsShim/build/Android.bp @@ -152,34 +152,6 @@ android_app { } //########################################################## -// Variant: System app upgrade - -android_app { - name: "CtsShimUpgrade", - - sdk_version: "current", - optimize: { - enabled: false, - }, - dex_preopt: { - enabled: false, - }, - - manifest: "shim/AndroidManifestUpgrade.xml", - min_sdk_version: "24", -} - -java_genrule { - name: "generate_shim_manifest", - srcs: [ - "shim/AndroidManifest.xml", - ":CtsShimUpgrade", - ], - out: ["AndroidManifest.xml"], - cmd: "sed -e s/__HASH__/`sha512sum -b $(location :CtsShimUpgrade) | cut -d' ' -f1`/ $(location shim/AndroidManifest.xml) > $(out)", -} - -//########################################################## // Variant: System app android_app { @@ -193,7 +165,7 @@ android_app { enabled: false, }, - manifest: ":generate_shim_manifest", + manifest: "shim/AndroidManifest.xml", apex_available: [ "//apex_available:platform", "com.android.apex.cts.shim.v1", diff --git a/packages/CtsShim/build/shim/AndroidManifest.xml b/packages/CtsShim/build/shim/AndroidManifest.xml index 3b8276b66bac..1ffe56c0c76a 100644 --- a/packages/CtsShim/build/shim/AndroidManifest.xml +++ b/packages/CtsShim/build/shim/AndroidManifest.xml @@ -17,15 +17,13 @@ <!-- Manifest for the system CTS shim --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - package="com.android.cts.ctsshim" - android:sharedUserId="com.android.cts.ctsshim" > + package="com.android.cts.ctsshim" > - <uses-sdk - android:minSdkVersion="24" + <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="28" /> <restrict-update - android:hash="__HASH__" /> + android:hash="__CAN_NOT_BE_UPDATED__" /> <application android:hasCode="false" diff --git a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml b/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml deleted file mode 100644 index 7f3644a18ad6..000000000000 --- a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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 for the system CTS shim --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - package="com.android.cts.ctsshim" > - - <uses-sdk - android:minSdkVersion="24" - android:targetSdkVersion="28" /> - - <application - android:hasCode="false" - tools:ignore="AllowBackup,MissingApplicationIcon" /> -</manifest> - diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp index 182daeb45d7c..203a3bf64910 100644 --- a/packages/SettingsLib/BannerMessagePreference/Android.bp +++ b/packages/SettingsLib/BannerMessagePreference/Android.bp @@ -31,5 +31,6 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.healthfitness", + "com.android.permission", ], } diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml new file mode 100644 index 000000000000..c999de7d99ea --- /dev/null +++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml @@ -0,0 +1,157 @@ +<!-- + ~ 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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector + android:width="112dp" + android:height="112dp" + android:viewportHeight="112" + android:viewportWidth="112"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="56.5" + android:translateY="56.625"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="0" + android:fillColor="?android:attr/textColorPrimary" + android:fillType="nonZero" + android:pathData=" M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c " + android:trimPathEnd="1" + android:trimPathOffset="0" + android:trimPathStart="0" /> + </group> + <group + android:name="_R_G_L_0_G" + android:rotation="-90" + android:translateX="56" + android:translateY="56"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:pathData=" M53.5 0 C53.5,-29.53 29.53,-53.5 0,-53.5 C0,-53.5 0,-53.5 0,-53.5 C-29.53,-53.5 -53.5,-29.53 -53.5,0 C-53.5,0 -53.5,0 -53.5,0 C-53.5,29.53 -29.53,53.5 0,53.5 C0,53.5 0,53.5 0,53.5 C29.53,53.5 53.5,29.53 53.5,0 C53.5,0 53.5,0 53.5,0c " + android:strokeAlpha="1" + android:strokeColor="?android:attr/textColorTertiary" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="5" + android:trimPathEnd="0" + android:trimPathOffset="0" + android:trimPathStart="0" /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="400" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="17" + android:propertyName="fillAlpha" + android:startOffset="400" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="400" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c " + android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="83" + android:propertyName="pathData" + android:startOffset="400" + android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c " + android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="167" + android:propertyName="pathData" + android:startOffset="483" + android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c " + android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 14.77,-13.41 14.77,-13.41 C14.77,-13.41 17.59,-10.59 17.59,-10.59 C17.59,-10.59 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="500" + android:propertyName="trimPathEnd" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="667" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml index b10ef6e77ec7..c448a2d434f8 100644 --- a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml +++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml @@ -28,81 +28,104 @@ android:orientation="vertical" style="@style/Banner.Preference.SettingsLib.Expressive"> - <RelativeLayout - android:id="@+id/top_row" + <FrameLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> + android:layout_height="wrap_content"> <LinearLayout + android:id="@+id/banner_content" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentStart="true" - android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4" android:orientation="vertical"> - <TextView - android:id="@+id/banner_header" + + <RelativeLayout + android:id="@+id/top_row" android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/Banner.Header.SettingsLib.Expressive"/> + android:orientation="horizontal"> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content"> - - <ImageView - android:id="@+id/banner_icon" - android:layout_width="@dimen/settingslib_expressive_space_small3" - android:layout_height="@dimen/settingslib_expressive_space_small3" - android:layout_gravity="center_vertical" - android:importantForAccessibility="no" - android:scaleType="fitCenter" /> - - <TextView - android:id="@+id/banner_title" + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/Banner.Title.SettingsLib.Expressive" /> - </LinearLayout> + android:layout_alignParentStart="true" + android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4" + android:orientation="vertical"> + <TextView + android:id="@+id/banner_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/Banner.Header.SettingsLib.Expressive"/> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ImageView + android:id="@+id/banner_icon" + android:layout_width="@dimen/settingslib_expressive_space_small3" + android:layout_height="@dimen/settingslib_expressive_space_small3" + android:layout_gravity="center_vertical" + android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall4" + android:importantForAccessibility="no" + android:scaleType="fitCenter" /> + + <TextView + android:id="@+id/banner_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/Banner.Title.SettingsLib.Expressive" /> + </LinearLayout> + + <TextView + android:id="@+id/banner_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/Banner.Subtitle.SettingsLib.Expressive"/> + </LinearLayout> + + <ImageButton + android:id="@+id/banner_dismiss_btn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/accessibility_banner_message_dismiss" + style="@style/Banner.Dismiss.SettingsLib.Expressive" /> + </RelativeLayout> <TextView - android:id="@+id/banner_subtitle" + android:id="@+id/banner_summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/Banner.Summary.SettingsLib.Expressive"/> + + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/Banner.Subtitle.SettingsLib.Expressive"/> + android:id="@+id/banner_buttons_frame" + android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6" + android:orientation="horizontal"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/banner_negative_btn" + android:layout_weight="1" + style="@style/Banner.NegativeButton.SettingsLib.Expressive"/> + <Space + android:id="@+id/banner_button_space" + android:layout_width="@dimen/settingslib_expressive_space_extrasmall4" + android:layout_height="@dimen/settingslib_expressive_space_small1"/> + <com.google.android.material.button.MaterialButton + android:id="@+id/banner_positive_btn" + android:layout_weight="1" + style="@style/Banner.PositiveButton.SettingsLib.Expressive"/> + </LinearLayout> </LinearLayout> - <ImageButton - android:id="@+id/banner_dismiss_btn" + <TextView + android:id="@+id/resolved_banner_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@style/Banner.Dismiss.SettingsLib.Expressive" /> - </RelativeLayout> - - <TextView - android:id="@+id/banner_summary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/Banner.Summary.SettingsLib.Expressive"/> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/banner_buttons_frame" - android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6" - android:orientation="horizontal"> - - <com.google.android.material.button.MaterialButton - android:id="@+id/banner_negative_btn" - android:layout_weight="1" - style="@style/Banner.NegativeButton.SettingsLib.Expressive"/> - <Space - android:layout_width="@dimen/settingslib_expressive_space_extrasmall4" - android:layout_height="@dimen/settingslib_expressive_space_small1"/> - <com.google.android.material.button.MaterialButton - android:id="@+id/banner_positive_btn" - android:layout_weight="1" - style="@style/Banner.PositiveButton.SettingsLib.Expressive"/> - </LinearLayout> + android:drawableTop="@drawable/settingslib_resolved_banner_avd" + android:visibility="gone" + style="@style/Banner.ResolvedText.SettingsLib.Expressive"/> + </FrameLayout> </com.android.settingslib.widget.BannerMessageView> </LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml index b864311bf9b7..09e07ccef683 100644 --- a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml +++ b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml @@ -33,7 +33,6 @@ <style name="Banner.Title.SettingsLib.Expressive" parent=""> <item name="android:layout_gravity">start</item> - <item name="android:layout_marginLeft">@dimen/settingslib_expressive_space_extrasmall4</item> <item name="android:textAlignment">viewStart</item> <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item> <item name="android:textColor">?android:attr/textColorPrimary</item> @@ -73,4 +72,11 @@ parent="@style/SettingsLibButtonStyle.Expressive.Outline.Extra"> <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item> </style> + + <style name="Banner.ResolvedText.SettingsLib.Expressive" parent=""> + <item name="android:layout_gravity">center</item> + <item name="android:drawablePadding">@dimen/settingslib_expressive_space_small1</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> </resources> diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java index c82829d6ccea..c90a76a39510 100644 --- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java +++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java @@ -35,6 +35,8 @@ import android.widget.TextView; import androidx.annotation.ColorInt; import androidx.annotation.ColorRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.preference.Preference; @@ -43,6 +45,9 @@ import androidx.preference.PreferenceViewHolder; import com.android.settingslib.widget.preference.banner.R; import com.google.android.material.button.MaterialButton; + +import java.util.Objects; + /** * Banner message is a banner displaying important information (permission request, page error etc), * and provide actions for user to address. It requires a user action to be dismissed. @@ -67,13 +72,15 @@ public class BannerMessagePreference extends Preference implements GroupSectionD R.color.banner_accent_attention_normal, R.color.settingslib_banner_button_background_normal); - // Corresponds to the enum valye of R.attr.attentionLevel + // Corresponds to the enum value of R.attr.attentionLevel private final int mAttrValue; @ColorRes private final int mBackgroundColorResId; @ColorRes private final int mAccentColorResId; @ColorRes private final int mButtonBackgroundColorResId; - AttentionLevel(int attrValue, @ColorRes int backgroundColorResId, + AttentionLevel( + int attrValue, + @ColorRes int backgroundColorResId, @ColorRes int accentColorResId, @ColorRes int buttonBackgroundColorResId) { mAttrValue = attrValue; @@ -115,33 +122,38 @@ public class BannerMessagePreference extends Preference implements GroupSectionD new BannerMessagePreference.DismissButtonInfo(); // Default attention level is High. - private AttentionLevel mAttentionLevel = AttentionLevel.HIGH; - private CharSequence mSubtitle; - private CharSequence mHeader; + @NonNull private AttentionLevel mAttentionLevel = AttentionLevel.HIGH; + @Nullable private CharSequence mSubtitle; + @Nullable private CharSequence mHeader; private int mButtonOrientation; + @Nullable private ResolutionAnimator.Data mResolutionData; - public BannerMessagePreference(Context context) { + public BannerMessagePreference(@NonNull Context context) { super(context); init(context, null /* attrs */); } - public BannerMessagePreference(Context context, AttributeSet attrs) { + public BannerMessagePreference(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context, attrs); } - public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr) { + public BannerMessagePreference( + @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } - public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr, + public BannerMessagePreference( + @NonNull Context context, + @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs); } - private void init(Context context, AttributeSet attrs) { + private void init(@NonNull Context context, @Nullable AttributeSet attrs) { setSelectable(false); int resId = SettingsThemeHelper.isExpressiveTheme(context) ? R.layout.settingslib_expressive_banner_message @@ -166,7 +178,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD } @Override - public void onBindViewHolder(PreferenceViewHolder holder) { + public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { super.onBindViewHolder(holder); final Context context = getContext(); @@ -193,14 +205,20 @@ public class BannerMessagePreference extends Preference implements GroupSectionD final ImageView iconView = (ImageView) holder.findViewById(R.id.banner_icon); if (iconView != null) { Drawable icon = getIcon(); - iconView.setImageDrawable( - icon == null - ? getContext().getDrawable(R.drawable.ic_warning) - : icon); - if (mAttentionLevel != AttentionLevel.NORMAL - && !SettingsThemeHelper.isExpressiveTheme(context)) { - iconView.setColorFilter( - new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN)); + + if (icon == null && SettingsThemeHelper.isExpressiveTheme(context)) { + iconView.setVisibility(View.GONE); + } else { + iconView.setVisibility(View.VISIBLE); + iconView.setImageDrawable( + icon == null + ? getContext().getDrawable(R.drawable.ic_warning) + : icon); + if (mAttentionLevel != AttentionLevel.NORMAL + && !SettingsThemeHelper.isExpressiveTheme(context)) { + iconView.setColorFilter( + new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN)); + } } } @@ -233,8 +251,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD mDismissButtonInfo.setUpButton(); final TextView subtitleView = (TextView) holder.findViewById(R.id.banner_subtitle); - subtitleView.setText(mSubtitle); - subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE); + if (subtitleView != null) { + subtitleView.setText(mSubtitle); + subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE); + } TextView headerView = (TextView) holder.findViewById(R.id.banner_header); if (headerView != null) { @@ -268,11 +288,25 @@ public class BannerMessagePreference extends Preference implements GroupSectionD linearLayout.setOrientation(mButtonOrientation); } } + + View buttonSpace = holder.findViewById(R.id.banner_button_space); + if (buttonSpace != null) { + if (mPositiveButtonInfo.shouldBeVisible() && mNegativeButtonInfo.shouldBeVisible()) { + buttonSpace.setVisibility(View.VISIBLE); + } else { + buttonSpace.setVisibility(View.GONE); + } + } + + if (mResolutionData != null) { + new ResolutionAnimator(mResolutionData, holder).startResolutionAnimation(); + } } /** - * Set the visibility state of positive button. + * Sets the visibility state of the positive button. */ + @NonNull public BannerMessagePreference setPositiveButtonVisible(boolean isVisible) { if (isVisible != mPositiveButtonInfo.mIsVisible) { mPositiveButtonInfo.mIsVisible = isVisible; @@ -282,8 +316,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD } /** - * Set the visibility state of negative button. + * Sets the visibility state of the negative button. */ + @NonNull public BannerMessagePreference setNegativeButtonVisible(boolean isVisible) { if (isVisible != mNegativeButtonInfo.mIsVisible) { mNegativeButtonInfo.mIsVisible = isVisible; @@ -293,9 +328,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD } /** - * Set the visibility state of dismiss button. + * Sets the visibility state of the dismiss button. */ @RequiresApi(Build.VERSION_CODES.S) + @NonNull public BannerMessagePreference setDismissButtonVisible(boolean isVisible) { if (isVisible != mDismissButtonInfo.mIsVisible) { mDismissButtonInfo.mIsVisible = isVisible; @@ -305,10 +341,35 @@ public class BannerMessagePreference extends Preference implements GroupSectionD } /** - * Register a callback to be invoked when positive button is clicked. + * Sets the enabled state of the positive button. + */ + @NonNull + public BannerMessagePreference setPositiveButtonEnabled(boolean isEnabled) { + if (isEnabled != mPositiveButtonInfo.mIsEnabled) { + mPositiveButtonInfo.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Sets the enabled state of the negative button. + */ + @NonNull + public BannerMessagePreference setNegativeButtonEnabled(boolean isEnabled) { + if (isEnabled != mNegativeButtonInfo.mIsEnabled) { + mNegativeButtonInfo.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Registers a callback to be invoked when positive button is clicked. */ + @NonNull public BannerMessagePreference setPositiveButtonOnClickListener( - View.OnClickListener listener) { + @Nullable View.OnClickListener listener) { if (listener != mPositiveButtonInfo.mListener) { mPositiveButtonInfo.mListener = listener; notifyChanged(); @@ -317,10 +378,11 @@ public class BannerMessagePreference extends Preference implements GroupSectionD } /** - * Register a callback to be invoked when negative button is clicked. + * Registers a callback to be invoked when negative button is clicked. */ + @NonNull public BannerMessagePreference setNegativeButtonOnClickListener( - View.OnClickListener listener) { + @Nullable View.OnClickListener listener) { if (listener != mNegativeButtonInfo.mListener) { mNegativeButtonInfo.mListener = listener; notifyChanged(); @@ -329,11 +391,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD } /** - * Register a callback to be invoked when the dismiss button is clicked. + * Registers a callback to be invoked when the dismiss button is clicked. */ @RequiresApi(Build.VERSION_CODES.S) + @NonNull public BannerMessagePreference setDismissButtonOnClickListener( - View.OnClickListener listener) { + @Nullable View.OnClickListener listener) { if (listener != mDismissButtonInfo.mListener) { mDismissButtonInfo.mListener = listener; notifyChanged(); @@ -344,6 +407,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD /** * Sets the text to be displayed in positive button. */ + @NonNull public BannerMessagePreference setPositiveButtonText(@StringRes int textResId) { return setPositiveButtonText(getContext().getString(textResId)); } @@ -351,7 +415,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD /** * Sets the text to be displayed in positive button. */ - public BannerMessagePreference setPositiveButtonText(CharSequence positiveButtonText) { + @NonNull + public BannerMessagePreference setPositiveButtonText( + @Nullable CharSequence positiveButtonText) { if (!TextUtils.equals(positiveButtonText, mPositiveButtonInfo.mText)) { mPositiveButtonInfo.mText = positiveButtonText; notifyChanged(); @@ -362,6 +428,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD /** * Sets the text to be displayed in negative button. */ + @NonNull public BannerMessagePreference setNegativeButtonText(@StringRes int textResId) { return setNegativeButtonText(getContext().getString(textResId)); } @@ -369,7 +436,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD /** * Sets the text to be displayed in negative button. */ - public BannerMessagePreference setNegativeButtonText(CharSequence negativeButtonText) { + @NonNull + public BannerMessagePreference setNegativeButtonText( + @Nullable CharSequence negativeButtonText) { if (!TextUtils.equals(negativeButtonText, mNegativeButtonInfo.mText)) { mNegativeButtonInfo.mText = negativeButtonText; notifyChanged(); @@ -380,8 +449,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD /** * Sets button orientation. */ - @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) + @NonNull public BannerMessagePreference setButtonOrientation(int orientation) { + if (!SettingsThemeHelper.isExpressiveTheme(getContext())) { + return this; + } + if (mButtonOrientation != orientation) { mButtonOrientation = orientation; notifyChanged(); @@ -393,6 +466,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD * Sets the subtitle. */ @RequiresApi(Build.VERSION_CODES.S) + @NonNull public BannerMessagePreference setSubtitle(@StringRes int textResId) { return setSubtitle(getContext().getString(textResId)); } @@ -401,7 +475,8 @@ public class BannerMessagePreference extends Preference implements GroupSectionD * Sets the subtitle. */ @RequiresApi(Build.VERSION_CODES.S) - public BannerMessagePreference setSubtitle(CharSequence subtitle) { + @NonNull + public BannerMessagePreference setSubtitle(@Nullable CharSequence subtitle) { if (!TextUtils.equals(subtitle, mSubtitle)) { mSubtitle = subtitle; notifyChanged(); @@ -412,7 +487,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD /** * Sets the header. */ - @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) + @NonNull public BannerMessagePreference setHeader(@StringRes int textResId) { return setHeader(getContext().getString(textResId)); } @@ -420,8 +495,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD /** * Sets the header. */ - @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) - public BannerMessagePreference setHeader(CharSequence header) { + @NonNull + public BannerMessagePreference setHeader(@Nullable CharSequence header) { + if (!SettingsThemeHelper.isExpressiveTheme(getContext())) { + return this; + } + if (!TextUtils.equals(header, mHeader)) { mHeader = header; notifyChanged(); @@ -430,32 +509,75 @@ public class BannerMessagePreference extends Preference implements GroupSectionD } /** - * Sets the attention level. This will update the color theme of the preference. + * Plays a resolution animation, showing the given message. */ - public BannerMessagePreference setAttentionLevel(AttentionLevel attentionLevel) { - if (attentionLevel == mAttentionLevel) { + @NonNull + public BannerMessagePreference showResolutionAnimation( + @NonNull CharSequence resolutionMessage, + @NonNull ResolutionCompletedCallback onCompletionCallback) { + if (!SettingsThemeHelper.isExpressiveTheme(getContext())) { return this; } + ResolutionAnimator.Data resolutionData = + new ResolutionAnimator.Data(resolutionMessage, onCompletionCallback); + if (!Objects.equals(mResolutionData, resolutionData)) { + mResolutionData = resolutionData; + notifyChanged(); + } + return this; + } - if (attentionLevel != null) { - mAttentionLevel = attentionLevel; + /** + * Removes the resolution animation from this preference. + * + * <p>Should be called after the resolution animation completes if this preference will be + * reused. Otherwise the resolution animation will be played everytime this preference is + * displayed. + */ + @NonNull + public BannerMessagePreference clearResolutionAnimation() { + if (!SettingsThemeHelper.isExpressiveTheme(getContext())) { + return this; + } + if (mResolutionData != null) { + mResolutionData = null; notifyChanged(); } return this; } + /** + * Sets the attention level. This will update the color theme of the preference. + */ + @NonNull + public BannerMessagePreference setAttentionLevel(@NonNull AttentionLevel attentionLevel) { + if (attentionLevel == mAttentionLevel) { + return this; + } + + mAttentionLevel = attentionLevel; + notifyChanged(); + return this; + } + static class ButtonInfo { - private Button mButton; - private CharSequence mText; - private View.OnClickListener mListener; + @Nullable private Button mButton; + @Nullable private CharSequence mText; + @Nullable private View.OnClickListener mListener; private boolean mIsVisible = true; + private boolean mIsEnabled = true; @ColorInt private int mColor; @ColorInt private int mBackgroundColor; - private ColorStateList mStrokeColor; + @Nullable private ColorStateList mStrokeColor; void setUpButton() { + if (mButton == null) { + return; + } + mButton.setText(mText); mButton.setOnClickListener(mListener); + mButton.setEnabled(mIsEnabled); MaterialButton btn = null; if (mButton instanceof MaterialButton) { @@ -492,11 +614,15 @@ public class BannerMessagePreference extends Preference implements GroupSectionD } static class DismissButtonInfo { - private ImageButton mButton; - private View.OnClickListener mListener; + @Nullable private ImageButton mButton; + @Nullable private View.OnClickListener mListener; private boolean mIsVisible = true; void setUpButton() { + if (mButton == null) { + return; + } + mButton.setOnClickListener(mListener); if (shouldBeVisible()) { mButton.setVisibility(View.VISIBLE); diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java index ff4e79ddaaa1..eabb6341c3dd 100644 --- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java +++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java @@ -23,6 +23,7 @@ import android.view.TouchDelegate; import android.view.View; import android.widget.LinearLayout; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settingslib.widget.preference.banner.R; @@ -34,22 +35,25 @@ import com.android.settingslib.widget.preference.banner.R; * {@link BannerMessagePreference} to a {@code PreferenceScreen}. */ public class BannerMessageView extends LinearLayout { - private Rect mTouchTargetForDismissButton; + @Nullable private Rect mTouchTargetForDismissButton; - public BannerMessageView(Context context) { + public BannerMessageView(@NonNull Context context) { super(context); } - public BannerMessageView(Context context, - @Nullable AttributeSet attrs) { + public BannerMessageView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } - public BannerMessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + public BannerMessageView( + @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } - public BannerMessageView(Context context, AttributeSet attrs, int defStyleAttr, + public BannerMessageView( + @NonNull Context context, + @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt new file mode 100644 index 000000000000..fbf910a42423 --- /dev/null +++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt @@ -0,0 +1,196 @@ +/* + * 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.settingslib.widget + +import android.graphics.drawable.Animatable2 +import android.graphics.drawable.AnimatedVectorDrawable +import android.graphics.drawable.Drawable +import android.os.Build +import android.provider.DeviceConfig +import android.transition.Fade +import android.transition.Transition +import android.transition.TransitionListenerAdapter +import android.transition.TransitionManager +import android.transition.TransitionSet +import android.view.View +import android.view.ViewGroup +import android.view.animation.LinearInterpolator +import android.widget.TextView +import androidx.preference.PreferenceViewHolder +import com.android.settingslib.widget.preference.banner.R +import java.time.Duration + +/** Callback to communicate when a banner message resolution animation is completed. */ +fun interface ResolutionCompletedCallback { + fun onCompleted() +} + +internal class ResolutionAnimator( + private val data: Data, + private val preferenceViewHolder: PreferenceViewHolder, +) { + + data class Data( + val resolutionMessage: CharSequence, + val resolutionCompletedCallback: ResolutionCompletedCallback, + ) + + private val defaultBannerContent: View? + get() = preferenceViewHolder.findView(R.id.banner_content) + private val resolvedTextView: TextView? + get() = preferenceViewHolder.findView(R.id.resolved_banner_text) + + fun startResolutionAnimation() { + resolvedTextView?.text = data.resolutionMessage + resolvedTextView?.resolutionDrawable?.reset() + + val transitionSet = + TransitionSet() + .setOrdering(TransitionSet.ORDERING_SEQUENTIAL) + .setInterpolator(linearInterpolator) + .addTransition(hideIssueContentTransition) + .addTransition( + showResolvedContentTransition + .clone() + .addListener( + object : TransitionListenerAdapter() { + override fun onTransitionEnd(transition: Transition) { + super.onTransitionEnd(transition) + startIssueResolvedAnimation() + } + } + ) + ) + + preferenceViewHolder.itemView.post { + TransitionManager.beginDelayedTransition( + preferenceViewHolder.itemView as ViewGroup, + transitionSet, + ) + + defaultBannerContent?.visibility = View.INVISIBLE + resolvedTextView?.visibility = View.VISIBLE + } + + preferenceViewHolder.itemView.addOnAttachStateChangeListener( + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) {} + + override fun onViewDetachedFromWindow(v: View) { + v.removeOnAttachStateChangeListener(this) + cancelAnimationsAndFinish() + } + } + ) + } + + private fun startIssueResolvedAnimation() { + val animatedDrawable = resolvedTextView?.resolutionDrawable + + if (animatedDrawable == null) { + hideResolvedUiAndFinish() + return + } + + animatedDrawable.apply { + clearAnimationCallbacks() + registerAnimationCallback( + object : Animatable2.AnimationCallback() { + override fun onAnimationEnd(drawable: Drawable) { + super.onAnimationEnd(drawable) + hideResolvedUiAndFinish() + } + } + ) + start() + } + } + + private fun hideResolvedUiAndFinish() { + val hideTransition = + hideResolvedContentTransition + .clone() + .setInterpolator(linearInterpolator) + .addListener( + object : TransitionListenerAdapter() { + override fun onTransitionEnd(transition: Transition) { + super.onTransitionEnd(transition) + data.resolutionCompletedCallback.onCompleted() + } + } + ) + TransitionManager.beginDelayedTransition( + preferenceViewHolder.itemView as ViewGroup, + hideTransition, + ) + resolvedTextView?.visibility = View.GONE + } + + private fun cancelAnimationsAndFinish() { + TransitionManager.endTransitions(preferenceViewHolder.itemView as ViewGroup) + + resolvedTextView?.visibility = View.GONE + + val animatedDrawable = resolvedTextView?.resolutionDrawable + animatedDrawable?.clearAnimationCallbacks() + animatedDrawable?.stop() + + data.resolutionCompletedCallback.onCompleted() + } + + private companion object { + private val linearInterpolator = LinearInterpolator() + + private val HIDE_ISSUE_CONTENT_TRANSITION_DURATION = Duration.ofMillis(333) + private val hideIssueContentTransition = + Fade(Fade.OUT).setDuration(HIDE_ISSUE_CONTENT_TRANSITION_DURATION.toMillis()) + + private val SHOW_RESOLVED_CONTENT_TRANSITION_DELAY = Duration.ofMillis(133) + private val SHOW_RESOLVED_CONTENT_TRANSITION_DURATION = Duration.ofMillis(250) + private val showResolvedContentTransition = + Fade(Fade.IN) + .setStartDelay(SHOW_RESOLVED_CONTENT_TRANSITION_DELAY.toMillis()) + .setDuration(SHOW_RESOLVED_CONTENT_TRANSITION_DURATION.toMillis()) + + private val hideResolvedContentTransitionDelay + get() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Duration.ofMillis( + DeviceConfig.getLong( + "settings_ui", + "banner_message_pref_hide_resolved_content_delay_millis", + 400, + ) + ) + } else { + Duration.ofMillis(400) + } + + private val HIDE_RESOLVED_UI_TRANSITION_DURATION = Duration.ofMillis(167) + private val hideResolvedContentTransition + get() = + Fade(Fade.OUT) + .setStartDelay(hideResolvedContentTransitionDelay.toMillis()) + .setDuration(HIDE_RESOLVED_UI_TRANSITION_DURATION.toMillis()) + + inline fun <reified T : View> PreferenceViewHolder.findView(id: Int): T? = + findViewById(id) as? T + + val TextView.resolutionDrawable: AnimatedVectorDrawable? + get() = compoundDrawables.find { it != null } as? AnimatedVectorDrawable + } +} diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp index a377f312ffbf..c8375a992d30 100644 --- a/packages/SettingsLib/ButtonPreference/Android.bp +++ b/packages/SettingsLib/ButtonPreference/Android.bp @@ -30,5 +30,6 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.healthfitness", + "com.android.permission", ], } diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml index dc5c9b297181..b6e80c784f10 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml @@ -18,7 +18,7 @@ <resources> <style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" > <item name="android:colorAccent">@color/settingslib_materialColorPrimary</item> - <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainerLowest</item> + <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item> <item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item> <item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item> <item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item> diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt index 6a0632073de9..a04fce7eeb86 100644 --- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt +++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt @@ -47,7 +47,7 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) : private val mHandler = Handler(Looper.getMainLooper()) - private val syncRunnable = Runnable { updatePreferences() } + private val syncRunnable = Runnable { updatePreferencesList() } init { val context = preferenceGroup.context @@ -64,7 +64,7 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) : true, /* resolveRefs */ ) mLegacyBackgroundRes = outValue.resourceId - updatePreferences() + updatePreferencesList() } @SuppressLint("RestrictedApi") @@ -82,7 +82,7 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) : updateBackground(holder, position) } - private fun updatePreferences() { + private fun updatePreferencesList() { val oldList = ArrayList(mRoundCornerMappingList) mRoundCornerMappingList = ArrayList() mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup) diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 522a436b0732..335f4a8293a0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -23,6 +23,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.hardware.input.InputManager; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; @@ -34,6 +35,7 @@ import android.sysprop.BluetoothProperties; import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import android.view.InputDevice; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; @@ -1193,4 +1195,53 @@ public class BluetoothUtils { } device.setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, fastPairCustomizedMeta.getBytes()); } + + /** + * Returns the {@link InputDevice} of the given bluetooth address if the device is a input + * device. + * + * @param address The address of the bluetooth device + * @return The {@link InputDevice} of the given address if applicable + */ + @Nullable + public static InputDevice getInputDevice(Context context, String address) { + InputManager im = context.getSystemService(InputManager.class); + + if (im != null) { + for (int deviceId : im.getInputDeviceIds()) { + String btAddress = im.getInputDeviceBluetoothAddress(deviceId); + + if (btAddress != null && btAddress.equals(address)) { + return im.getInputDevice(deviceId); + } + } + } + return null; + } + + /** + * Identifies whether a device is a stylus using the associated {@link InputDevice} or + * {@link CachedBluetoothDevice}. + * InputDevices are only available when the device is USI or Bluetooth-connected, whereas + * CachedBluetoothDevices are available for Bluetooth devices when connected or paired, + * so to handle all cases, both are needed. + * + * @param inputDevice The associated input device of the stylus + * @param cachedBluetoothDevice The associated bluetooth device of the stylus + */ + public static boolean isDeviceStylus(@Nullable InputDevice inputDevice, + @Nullable CachedBluetoothDevice cachedBluetoothDevice) { + if (inputDevice != null && inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)) { + return true; + } + + if (cachedBluetoothDevice != null) { + BluetoothDevice bluetoothDevice = cachedBluetoothDevice.getDevice(); + String deviceType = BluetoothUtils.getStringMetaData(bluetoothDevice, + BluetoothDevice.METADATA_DEVICE_TYPE); + return TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_STYLUS); + } + + return false; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 3017d79836e8..367e38ed779d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -63,6 +63,7 @@ import com.google.common.collect.ImmutableList; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -70,7 +71,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -108,6 +108,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { private static final String SYSUI_PKG = "com.android.systemui"; private static final String TAG = "LocalBluetoothLeBroadcast"; private static final boolean DEBUG = BluetoothUtils.D; + private static final String VALID_PASSWORD_CHARACTERS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:," + + ".<>?/"; + private static final int PASSWORD_LENGTH = 16; static final String NAME = "LE_AUDIO_BROADCAST"; private static final String UNDERLINE = "_"; @@ -1088,11 +1092,16 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; } - private String generateRandomPassword() { - String randomUUID = UUID.randomUUID().toString(); - // first 16 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx - return randomUUID.substring(0, 8) + randomUUID.substring(9, 13) + randomUUID.substring(14, - 18); + private static String generateRandomPassword() { + SecureRandom random = new SecureRandom(); + StringBuilder stringBuilder = new StringBuilder(PASSWORD_LENGTH); + + for (int i = 0; i < PASSWORD_LENGTH; i++) { + int randomIndex = random.nextInt(VALID_PASSWORD_CHARACTERS.length()); + stringBuilder.append(VALID_PASSWORD_CHARACTERS.charAt(randomIndex)); + } + + return stringBuilder.toString(); } private void registerContentObserver() { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java index abc1d226972b..e4381ae0a663 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.when; import android.bluetooth.AudioInputControl; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.Context; import androidx.test.core.app.ApplicationProvider; @@ -67,6 +68,9 @@ public class AmbientVolumeControllerTest { @Before public void setUp() { when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile); + when(mVolumeControlProfile.isProfileReady()).thenReturn(true); + when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn( + BluetoothProfile.STATE_CONNECTED); when(mDevice.getAddress()).thenReturn(TEST_ADDRESS); when(mDevice.isConnected()).thenReturn(true); mVolumeController = new AmbientVolumeController(mProfileManager, mCallback); @@ -74,8 +78,6 @@ public class AmbientVolumeControllerTest { @Test public void onServiceConnected_notifyCallback() { - when(mVolumeControlProfile.isProfileReady()).thenReturn(true); - mVolumeController.onServiceConnected(); verify(mCallback).onVolumeControlServiceConnected(); 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 ebe6128e5582..0325c0ec7915 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 @@ -15,7 +15,9 @@ */ package com.android.settingslib.bluetooth; +import static com.android.settingslib.bluetooth.BluetoothUtils.getInputDevice; import static com.android.settingslib.bluetooth.BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice; +import static com.android.settingslib.bluetooth.BluetoothUtils.isDeviceStylus; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER; import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA; @@ -42,6 +44,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; +import android.hardware.input.InputManager; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; @@ -51,6 +54,7 @@ import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.util.Pair; +import android.view.InputDevice; import com.android.internal.R; import com.android.settingslib.flags.Flags; @@ -97,14 +101,18 @@ public class BluetoothUtilsTest { @Mock private LocalBluetoothLeBroadcastAssistant mAssistant; @Mock private CachedBluetoothDeviceManager mDeviceManager; @Mock private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState; + @Mock + private InputManager mInputManager; private Context mContext; private ShadowBluetoothAdapter mShadowBluetoothAdapter; + private final InputDevice mInputDevice = mock(InputDevice.class); private static final String STRING_METADATA = "string_metadata"; private static final String LE_AUDIO_SHARING_METADATA = "le_audio_sharing"; private static final String BOOL_METADATA = "true"; private static final String INT_METADATA = "25"; private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; + private static final int TEST_DEVICE_ID = 123; private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH"; private static final String CONTROL_METADATA = "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" @@ -115,6 +123,7 @@ public class BluetoothUtilsTest { 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 String TEST_ADDRESS = "11:22:33:44:55:66"; private static final int TEST_BROADCAST_ID = 25; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -134,6 +143,10 @@ public class BluetoothUtilsTest { when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP); when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO); when(mHearingAid.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID); + when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager); + when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{TEST_DEVICE_ID}); + when(mInputManager.getInputDeviceBluetoothAddress(TEST_DEVICE_ID)).thenReturn(TEST_ADDRESS); + when(mInputManager.getInputDevice(TEST_DEVICE_ID)).thenReturn(mInputDevice); } @Test @@ -1097,9 +1110,8 @@ public class BluetoothUtilsTest { @Test public void getAudioDeviceAttributesForSpatialAudio_bleHeadset() { - String address = "11:22:33:44:55:66"; when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); - when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS); when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile)); when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true); @@ -1112,14 +1124,13 @@ public class BluetoothUtilsTest { new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLE_HEADSET, - address)); + TEST_ADDRESS)); } @Test public void getAudioDeviceAttributesForSpatialAudio_bleSpeaker() { - String address = "11:22:33:44:55:66"; when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); - when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS); when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile)); when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true); @@ -1132,14 +1143,14 @@ public class BluetoothUtilsTest { new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLE_SPEAKER, - address)); + TEST_ADDRESS)); } @Test public void getAudioDeviceAttributesForSpatialAudio_a2dp() { - String address = "11:22:33:44:55:66"; + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); - when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS); when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mA2dpProfile)); when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(true); @@ -1152,14 +1163,13 @@ public class BluetoothUtilsTest { new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, - address)); + TEST_ADDRESS)); } @Test public void getAudioDeviceAttributesForSpatialAudio_hearingAid() { - String address = "11:22:33:44:55:66"; when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); - when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS); when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mHearingAid)); when(mHearingAid.isEnabled(mBluetoothDevice)).thenReturn(true); @@ -1172,7 +1182,7 @@ public class BluetoothUtilsTest { new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HEARING_AID, - address)); + TEST_ADDRESS)); } @Test @@ -1375,4 +1385,54 @@ public class BluetoothUtilsTest { verify(mBluetoothDevice).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, TEMP_BOND_METADATA.getBytes()); } + + @Test + public void getInputDevice_addressNotMatched_returnsNull() { + assertThat(getInputDevice(mContext, "123")).isNull(); + } + + @Test + public void getInputDevice_isInputDevice_returnsInputDevice() { + assertThat(getInputDevice(mContext, TEST_ADDRESS)).isEqualTo(mInputDevice); + } + + @Test + public void isDeviceStylus_noDevices_false() { + assertThat(isDeviceStylus(null, null)).isFalse(); + } + + @Test + public void isDeviceStylus_nonStylusInputDevice_false() { + InputDevice inputDevice = new InputDevice.Builder() + .setSources(InputDevice.SOURCE_DPAD) + .build(); + + assertThat(isDeviceStylus(inputDevice, null)).isFalse(); + } + + @Test + public void isDeviceStylus_stylusInputDevice_true() { + InputDevice inputDevice = new InputDevice.Builder() + .setSources(InputDevice.SOURCE_STYLUS) + .build(); + + assertThat(isDeviceStylus(inputDevice, null)).isTrue(); + } + + @Test + public void isDeviceStylus_nonStylusBluetoothDevice_false() { + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn( + BluetoothDevice.DEVICE_TYPE_WATCH.getBytes()); + + assertThat(isDeviceStylus(null, mCachedBluetoothDevice)).isFalse(); + } + + @Test + public void isDeviceStylus_stylusBluetoothDevice_true() { + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn( + BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes()); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + + assertThat(isDeviceStylus(null, mCachedBluetoothDevice)).isTrue(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 69e99c616910..94199df4b9f8 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -662,10 +662,10 @@ public class CachedBluetoothDeviceManagerTest { @Test @RequiresFlagsEnabled( com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) - public void onDeviceUnpaired_hearingDevice_callReportConnectionStatus() { + public void onDeviceUnpaired_containsHearingAidInfo_callReportConnectionStatus() { when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mCachedDevice1.getProfiles()).thenReturn( - ImmutableList.of(mHapClientProfile, mHearingAidProfile)); + mCachedDevice1.setHearingAidInfo( + new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build()); mCachedDeviceManager.onDeviceUnpaired(mCachedDevice1); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 85617bad1a91..70c042cb8eba 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -811,7 +811,7 @@ public class SettingsBackupTest { Settings.Secure.V_TO_U_RESTORE_ALLOWLIST, Settings.Secure.V_TO_U_RESTORE_DENYLIST, Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI, - Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY); + Settings.Secure.OTP_NOTIFICATION_REDACTION_LOCK_TIME); @Test public void systemSettingsBackedUpOrDenied() { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt index f8bcb81dc073..bc75b1dad40c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt @@ -22,19 +22,8 @@ import android.util.Log import android.util.LruCache import android.util.MathUtils import androidx.annotation.VisibleForTesting -import java.lang.Float.max -import java.lang.Float.min import kotlin.math.roundToInt -private const val TAG_WGHT = "wght" -private const val TAG_ITAL = "ital" - -private const val FONT_WEIGHT_DEFAULT_VALUE = 400f -private const val FONT_ITALIC_MAX = 1f -private const val FONT_ITALIC_MIN = 0f -private const val FONT_ITALIC_ANIMATION_STEP = 0.1f -private const val FONT_ITALIC_DEFAULT_VALUE = 0f - /** Caches for font interpolation */ interface FontCache { val animationFrameCount: Int @@ -91,11 +80,8 @@ class FontCacheImpl(override val animationFrameCount: Int = DEFAULT_FONT_CACHE_M class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { /** Linear interpolate the font variation settings. */ fun lerp(start: Font, end: Font, progress: Float, linearProgress: Float): Font { - if (progress == 0f) { - return start - } else if (progress == 1f) { - return end - } + if (progress <= 0f) return start + if (progress >= 1f) return end val startAxes = start.axes ?: EMPTY_AXES val endAxes = end.axes ?: EMPTY_AXES @@ -110,7 +96,7 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { InterpKey(start, end, (linearProgress * fontCache.animationFrameCount).roundToInt()) fontCache.get(iKey)?.let { if (DEBUG) { - Log.d(LOG_TAG, "[$progress] Interp. cache hit for $iKey") + Log.d(LOG_TAG, "[$progress, $linearProgress] Interp. cache hit for $iKey") } return it } @@ -121,37 +107,16 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { // and also pre-fill the missing axes value with default value from 'fvar' table. val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue -> - when (tag) { - TAG_WGHT -> - MathUtils.lerp( - startValue ?: FONT_WEIGHT_DEFAULT_VALUE, - endValue ?: FONT_WEIGHT_DEFAULT_VALUE, - progress, - ) - TAG_ITAL -> - adjustItalic( - MathUtils.lerp( - startValue ?: FONT_ITALIC_DEFAULT_VALUE, - endValue ?: FONT_ITALIC_DEFAULT_VALUE, - progress, - ) - ) - else -> { - require(startValue != null && endValue != null) { - "Unable to interpolate due to unknown default axes value : $tag" - } - MathUtils.lerp(startValue, endValue, progress) - } - } + MathUtils.lerp(startValue, endValue, progress) } // Check if we already make font for this axes. This is typically happens if the animation - // happens backward. + // happens backward and is being linearly interpolated. val vKey = VarFontKey(start, newAxes) fontCache.get(vKey)?.let { fontCache.put(iKey, it) if (DEBUG) { - Log.d(LOG_TAG, "[$progress] Axis cache hit for $vKey") + Log.d(LOG_TAG, "[$progress, $linearProgress] Axis cache hit for $vKey") } return it } @@ -164,14 +129,14 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { fontCache.put(vKey, newFont) // Cache misses are likely to create memory leaks, so this is logged at error level. - Log.e(LOG_TAG, "[$progress] Cache MISS for $iKey / $vKey") + Log.e(LOG_TAG, "[$progress, $linearProgress] Cache MISS for $iKey / $vKey") return newFont } private fun lerp( start: Array<FontVariationAxis>, end: Array<FontVariationAxis>, - filter: (tag: String, left: Float?, right: Float?) -> Float, + filter: (tag: String, left: Float, right: Float) -> Float, ): List<FontVariationAxis> { // Safe to modify result of Font#getAxes since it returns cloned object. start.sortBy { axis -> axis.tag } @@ -191,39 +156,37 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { else -> tagA.compareTo(tagB) } - val axis = + val tag = + when { + comp == 0 -> tagA!! + comp < 0 -> tagA!! + else -> tagB!! + } + + val axisDefinition = GSFAxes.getAxis(tag) + require(comp == 0 || axisDefinition != null) { + "Unable to interpolate due to unknown default axes value: $tag" + } + + val axisValue = when { - comp == 0 -> { - val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue) - FontVariationAxis(tagA, v) - } - comp < 0 -> { - val v = filter(tagA!!, start[i++].styleValue, null) - FontVariationAxis(tagA, v) - } - else -> { // comp > 0 - val v = filter(tagB!!, null, end[j++].styleValue) - FontVariationAxis(tagB, v) - } + comp == 0 -> filter(tag, start[i++].styleValue, end[j++].styleValue) + comp < 0 -> filter(tag, start[i++].styleValue, axisDefinition!!.defaultValue) + else -> filter(tag, axisDefinition!!.defaultValue, end[j++].styleValue) } - result.add(axis) + // Round axis value to valid intermediate steps. This improves the cache hit rate. + val step = axisDefinition?.animationStep ?: DEFAULT_ANIMATION_STEP + result.add(FontVariationAxis(tag, (axisValue / step).roundToInt() * step)) } return result } - // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps - // Cache hit ratio in the Skia glyph cache. - private fun adjustItalic(value: Float) = - coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP) - - private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) = - (v.coerceIn(min, max) / step).toInt() * step - companion object { private const val LOG_TAG = "FontInterpolator" private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) private val EMPTY_AXES = arrayOf<FontVariationAxis>() + private const val DEFAULT_ANIMATION_STEP = 1f // Returns true if given two font instance can be interpolated. fun canInterpolate(start: Font, end: Font) = 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 9545bda80b2d..9a746870c6ff 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt @@ -1,12 +1,20 @@ -package com.android.systemui.animation +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -object GSFAxes { - const val WEIGHT = "wght" - const val WIDTH = "wdth" - const val SLANT = "slnt" - const val ROUND = "ROND" - const val OPTICAL_SIZE = "opsz" -} +package com.android.systemui.animation class FontVariationUtils { private var mWeight = -1 @@ -46,20 +54,20 @@ class FontVariationUtils { } var resultString = "" if (mWeight >= 0) { - resultString += "'${GSFAxes.WEIGHT}' $mWeight" + resultString += "'${GSFAxes.WEIGHT.tag}' $mWeight" } if (mWidth >= 0) { resultString += - (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH}' $mWidth" + (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH.tag}' $mWidth" } if (mOpticalSize >= 0) { resultString += (if (resultString.isBlank()) "" else ", ") + - "'${GSFAxes.OPTICAL_SIZE}' $mOpticalSize" + "'${GSFAxes.OPTICAL_SIZE.tag}' $mOpticalSize" } if (mRoundness >= 0) { resultString += - (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND}' $mRoundness" + (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND.tag}' $mRoundness" } return if (isUpdated) resultString else "" } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt new file mode 100644 index 000000000000..f4e03613169a --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt @@ -0,0 +1,98 @@ +/* + * 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.animation + +data class AxisDefinition( + val tag: String, + val minValue: Float, + val defaultValue: Float, + val maxValue: Float, + val animationStep: Float, +) + +object GSFAxes { + val WEIGHT = + AxisDefinition( + tag = "wght", + minValue = 1f, + defaultValue = 400f, + maxValue = 1000f, + animationStep = 10f, + ) + + val WIDTH = + AxisDefinition( + tag = "wdth", + minValue = 25f, + defaultValue = 100f, + maxValue = 151f, + animationStep = 1f, + ) + + val SLANT = + AxisDefinition( + tag = "slnt", + minValue = 0f, + defaultValue = 0f, + maxValue = -10f, + animationStep = 0.1f, + ) + + val ROUND = + AxisDefinition( + tag = "ROND", + minValue = 0f, + defaultValue = 0f, + maxValue = 100f, + animationStep = 1f, + ) + + val GRADE = + AxisDefinition( + tag = "GRAD", + minValue = 0f, + defaultValue = 0f, + maxValue = 100f, + animationStep = 1f, + ) + + val OPTICAL_SIZE = + AxisDefinition( + tag = "opsz", + minValue = 6f, + defaultValue = 18f, + maxValue = 144f, + animationStep = 1f, + ) + + // Not a GSF Axis, but present for FontInterpolator compatibility + val ITALIC = + AxisDefinition( + tag = "ITAL", + minValue = 0f, + defaultValue = 0f, + maxValue = 1f, + animationStep = 0.1f, + ) + + private val AXIS_MAP = + listOf(WEIGHT, WIDTH, SLANT, ROUND, GRADE, OPTICAL_SIZE, ITALIC) + .map { def -> def.tag.toLowerCase() to def } + .toMap() + + fun getAxis(axis: String): AxisDefinition? = AXIS_MAP[axis.toLowerCase()] +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt index 297995becfb2..7cd6c6b47f2a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt @@ -124,7 +124,6 @@ constructor( SmallClock( burnInParams = burnIn.parameters, onTopChanged = burnIn.onSmallClockTopChanged, - modifier = Modifier.fillMaxWidth(), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index cdb1e2e53b09..7e7b6297406e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -54,6 +54,7 @@ import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.res.R +import androidx.compose.ui.unit.Dp /** Renders a lightweight shade UI container, as an overlay. */ @Composable @@ -202,10 +203,15 @@ object OverlayShade { } object Dimensions { - val PanelCornerRadius = 46.dp + val PanelCornerRadius: Dp + @Composable + @ReadOnlyComposable get() = + dimensionResource(R.dimen.overlay_shade_panel_shape_radius) } object Shapes { - val RoundedCornerPanel = RoundedCornerShape(Dimensions.PanelCornerRadius) + val RoundedCornerPanel: RoundedCornerShape + @Composable + @ReadOnlyComposable get() = RoundedCornerShape(Dimensions.PanelCornerRadius) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index 5e12ee1b18fa..db9035b1635b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -65,6 +65,7 @@ import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateElementFloatAsState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.thenIf +import com.android.compose.theme.colorAttr import com.android.settingslib.Utils import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -75,8 +76,6 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipBackground -import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipHighlighted import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.onScrimDim import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingHorizontal import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingVertical @@ -107,7 +106,7 @@ object ShadeHeader { object Dimensions { val CollapsedHeight = 48.dp val ExpandedHeight = 120.dp - val ChipPaddingHorizontal = 8.dp + val ChipPaddingHorizontal = 6.dp val ChipPaddingVertical = 4.dp } @@ -117,12 +116,6 @@ object ShadeHeader { val ColorScheme.onScrimDim: Color get() = Color.DarkGray - - val ColorScheme.chipBackground: Color - get() = Color.DarkGray - - val ColorScheme.chipHighlighted: Color - get() = Color.LightGray } object TestTags { @@ -165,7 +158,7 @@ fun ContentScope.CollapsedShadeHeader( VariableDayDate( longerDateText = viewModel.longerDateText, shorterDateText = viewModel.shorterDateText, - chipHighlight = viewModel.notificationsChipHighlight, + textColor = colorAttr(android.R.attr.textColorPrimary), modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart), ) } @@ -265,7 +258,7 @@ fun ContentScope.ExpandedShadeHeader( VariableDayDate( longerDateText = viewModel.longerDateText, shorterDateText = viewModel.shorterDateText, - chipHighlight = viewModel.notificationsChipHighlight, + textColor = colorAttr(android.R.attr.textColorPrimary), modifier = Modifier.widthIn(max = 90.dp), ) Spacer(modifier = Modifier.weight(1f)) @@ -310,6 +303,7 @@ fun ContentScope.OverlayShadeHeader( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = horizontalPadding), ) { + val chipHighlight = viewModel.notificationsChipHighlight if (isShadeLayoutWide) { Clock( scale = 1f, @@ -319,13 +313,13 @@ fun ContentScope.OverlayShadeHeader( Spacer(modifier = Modifier.width(5.dp)) } NotificationsChip( - chipHighlight = viewModel.notificationsChipHighlight, onClick = viewModel::onNotificationIconChipClicked, + backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme), ) { VariableDayDate( longerDateText = viewModel.longerDateText, shorterDateText = viewModel.shorterDateText, - chipHighlight = viewModel.notificationsChipHighlight, + textColor = chipHighlight.foregroundColor(MaterialTheme.colorScheme), ) } } @@ -338,14 +332,13 @@ fun ContentScope.OverlayShadeHeader( ) { val chipHighlight = viewModel.quickSettingsChipHighlight SystemIconChip( - chipHighlight = chipHighlight, + backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme), onClick = viewModel::onSystemIconChipClicked, ) { StatusIcons( viewModel = viewModel, useExpandedFormat = false, modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false), - chipHighlight = chipHighlight, ) BatteryIcon( createBatteryMeterViewController = @@ -515,6 +508,7 @@ private fun BatteryIcon( batteryIcon.setPercentShowMode( if (useExpandedFormat) BatteryMeterView.MODE_ESTIMATE else BatteryMeterView.MODE_ON ) + // TODO(b/397223606): Get the actual spec for this. if (chipHighlight is HeaderChipHighlight.Strong) { batteryIcon.updateColors(primaryColor, inverseColor, inverseColor) } else if (chipHighlight is HeaderChipHighlight.Weak) { @@ -553,7 +547,6 @@ private fun ContentScope.StatusIcons( viewModel: ShadeHeaderViewModel, useExpandedFormat: Boolean, modifier: Modifier = Modifier, - chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None, ) { val localContext = LocalContext.current val themedContext = @@ -581,6 +574,8 @@ private fun ContentScope.StatusIcons( viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS) } + val chipHighlight = viewModel.quickSettingsChipHighlight + AndroidView( factory = { context -> iconManager.setTint(primaryColor, inverseColor) @@ -617,6 +612,7 @@ private fun ContentScope.StatusIcons( iconContainer.removeIgnoredSlot(locationSlot) } + // TODO(b/397223606): Get the actual spec for this. if (chipHighlight is HeaderChipHighlight.Strong) { iconManager.setTint(inverseColor, primaryColor) } else if (chipHighlight is HeaderChipHighlight.Weak) { @@ -629,16 +625,12 @@ private fun ContentScope.StatusIcons( @Composable private fun NotificationsChip( - chipHighlight: HeaderChipHighlight, onClick: () -> Unit, modifier: Modifier = Modifier, + backgroundColor: Color = Color.Unspecified, content: @Composable BoxScope.() -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } - val chipBackground = - with(MaterialTheme.colorScheme) { - if (chipHighlight is HeaderChipHighlight.Strong) chipHighlighted else chipBackground - } Box( modifier = modifier @@ -647,7 +639,7 @@ private fun NotificationsChip( indication = null, onClick = onClick, ) - .background(chipBackground, RoundedCornerShape(25.dp)) + .background(backgroundColor, RoundedCornerShape(25.dp)) .padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical) ) { content() @@ -657,7 +649,7 @@ private fun NotificationsChip( @Composable private fun SystemIconChip( modifier: Modifier = Modifier, - chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None, + backgroundColor: Color = Color.Unspecified, onClick: (() -> Unit)? = null, content: @Composable RowScope.() -> Unit, ) { @@ -667,16 +659,12 @@ private fun SystemIconChip( with(MaterialTheme.colorScheme) { Modifier.background(onScrimDim, RoundedCornerShape(CollapsedHeight / 4)) } - val backgroundColor = - with(MaterialTheme.colorScheme) { - if (chipHighlight is HeaderChipHighlight.Strong) chipHighlighted else chipBackground - } Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier - .thenIf(chipHighlight !is HeaderChipHighlight.None) { + .thenIf(backgroundColor != Color.Unspecified) { Modifier.background(backgroundColor, RoundedCornerShape(25.dp)) .padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt index 64aada52626b..8fbd0519cbdf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt @@ -4,22 +4,16 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.Layout -import com.android.compose.theme.colorAttr -import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight @Composable fun VariableDayDate( longerDateText: String, shorterDateText: String, - chipHighlight: HeaderChipHighlight, + textColor: Color, modifier: Modifier = Modifier, ) { - val textColor = - if (chipHighlight is HeaderChipHighlight.Strong) - colorAttr(android.R.attr.textColorPrimaryInverse) - else colorAttr(android.R.attr.textColorPrimary) - Layout( contents = listOf( 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 004d1aa1fe93..ac1c5a8dfaf3 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 @@ -130,39 +130,25 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController private val FONT_AXES = listOf( - ClockFontAxis( - key = GSFAxes.WEIGHT, + GSFAxes.WEIGHT.toClockAxis( type = AxisType.Float, - minValue = 25f, currentValue = 400f, - maxValue = 1000f, name = "Weight", description = "Glyph Weight", ), - ClockFontAxis( - key = GSFAxes.WIDTH, + GSFAxes.WIDTH.toClockAxis( type = AxisType.Float, - minValue = 25f, currentValue = 85f, - maxValue = 151f, name = "Width", description = "Glyph Width", ), - ClockFontAxis( - key = GSFAxes.ROUND, + GSFAxes.ROUND.toClockAxis( type = AxisType.Boolean, - minValue = 0f, - currentValue = 0f, - maxValue = 100f, name = "Round", description = "Glyph Roundness", ), - ClockFontAxis( - key = GSFAxes.SLANT, + GSFAxes.SLANT.toClockAxis( type = AxisType.Boolean, - minValue = 0f, - currentValue = 0f, - maxValue = -10f, name = "Slant", description = "Glyph Slant", ), @@ -170,10 +156,10 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController private val LEGACY_FLEX_SETTINGS = listOf( - ClockFontAxisSetting(GSFAxes.WEIGHT, 600f), - ClockFontAxisSetting(GSFAxes.WIDTH, 100f), - ClockFontAxisSetting(GSFAxes.ROUND, 100f), - ClockFontAxisSetting(GSFAxes.SLANT, 0f), + GSFAxes.WEIGHT.toClockAxisSetting(600f), + GSFAxes.WIDTH.toClockAxisSetting(100f), + GSFAxes.ROUND.toClockAxisSetting(100f), + GSFAxes.SLANT.toClockAxisSetting(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 b2dbd6552955..b4c2f5de290f 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 @@ -132,7 +132,7 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: if (!isLargeClock) { axes = axes.map { axis -> - if (axis.key == GSFAxes.WIDTH && axis.value > SMALL_CLOCK_MAX_WDTH) { + if (axis.key == GSFAxes.WIDTH.tag && 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/FontUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt new file mode 100644 index 000000000000..212b1e29d1b8 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt @@ -0,0 +1,43 @@ +/* + * 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.shared.clocks + +import com.android.systemui.animation.AxisDefinition +import com.android.systemui.plugins.clocks.AxisType +import com.android.systemui.plugins.clocks.ClockFontAxis +import com.android.systemui.plugins.clocks.ClockFontAxisSetting + +fun AxisDefinition.toClockAxis( + type: AxisType, + currentValue: Float? = null, + name: String, + description: String, +): ClockFontAxis { + return ClockFontAxis( + key = this.tag, + type = type, + maxValue = this.maxValue, + minValue = this.minValue, + currentValue = currentValue ?: this.defaultValue, + name = name, + description = description, + ) +} + +fun AxisDefinition.toClockAxisSetting(value: Float? = null): ClockFontAxisSetting { + return ClockFontAxisSetting(this.tag, value ?: this.defaultValue) +} 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 54be4d81ea06..b7ce20ec32d1 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 @@ -49,6 +49,7 @@ import com.android.systemui.shared.clocks.DigitTranslateAnimator import com.android.systemui.shared.clocks.DimensionParser import com.android.systemui.shared.clocks.FLEX_CLOCK_ID import com.android.systemui.shared.clocks.FontTextStyle +import com.android.systemui.shared.clocks.toClockAxisSetting import java.lang.Thread import kotlin.math.max import kotlin.math.min @@ -585,25 +586,25 @@ open class SimpleDigitalClockTextView( val FIDGET_INTERPOLATOR = PathInterpolator(0.26873f, 0f, 0.45042f, 1f) val FIDGET_DISTS = mapOf( - GSFAxes.WEIGHT to Pair(200f, 500f), - GSFAxes.WIDTH to Pair(30f, 75f), - GSFAxes.ROUND to Pair(0f, 50f), - GSFAxes.SLANT to Pair(0f, -5f), + GSFAxes.WEIGHT.tag to Pair(200f, 500f), + GSFAxes.WIDTH.tag to Pair(30f, 75f), + GSFAxes.ROUND.tag to Pair(0f, 50f), + GSFAxes.SLANT.tag to Pair(0f, -5f), ) val AOD_COLOR = Color.WHITE - val LS_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 400f) - val AOD_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 200f) - val WIDTH_AXIS = ClockFontAxisSetting(GSFAxes.WIDTH, 85f) - val ROUND_AXIS = ClockFontAxisSetting(GSFAxes.ROUND, 0f) - val SLANT_AXIS = ClockFontAxisSetting(GSFAxes.SLANT, 0f) + val LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(400f) + val AOD_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(200f) + val WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(85f) + val ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(0f) + val SLANT_AXIS = GSFAxes.SLANT.toClockAxisSetting(0f) // Axes for Legacy version of the Flex Clock - val FLEX_LS_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 600f) - val FLEX_AOD_LARGE_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 74f) - val FLEX_AOD_SMALL_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 133f) - val FLEX_LS_WIDTH_AXIS = ClockFontAxisSetting(GSFAxes.WIDTH, 100f) - val FLEX_AOD_WIDTH_AXIS = ClockFontAxisSetting(GSFAxes.WIDTH, 43f) - val FLEX_ROUND_AXIS = ClockFontAxisSetting(GSFAxes.ROUND, 100f) + val FLEX_LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(600f) + val FLEX_AOD_LARGE_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(74f) + val FLEX_AOD_SMALL_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(133f) + val FLEX_LS_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(100f) + val FLEX_AOD_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(43f) + val FLEX_ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(100f) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt index c42e25b20e0d..d046ad114fa5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt @@ -103,7 +103,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() { fun testUpdateEmergencyButton() { Mockito.`when`(telecomManager.isInCall).thenReturn(true) Mockito.`when`(lockPatternUtils.isSecure(anyInt())).thenReturn(true) - Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) + Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) .thenReturn(true) underTest.updateEmergencyCallButton() backgroundExecutor.runAllReady() @@ -112,7 +112,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() { /* isInCall= */ any(), /* hasTelephonyRadio= */ any(), /* simLocked= */ any(), - /* isSecure= */ any() + /* isSecure= */ any(), ) mainExecutor.runAllReady() verify(emergencyButton) @@ -120,7 +120,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() { /* isInCall= */ eq(true), /* hasTelephonyRadio= */ eq(true), /* simLocked= */ any(), - /* isSecure= */ eq(true) + /* isSecure= */ eq(true), ) } 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 f44769d522eb..8d3640d8d809 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt @@ -21,7 +21,7 @@ class FontVariationUtilsTest : SysuiTestCase() { roundness = 100, ) Assert.assertEquals( - "'${GSFAxes.WEIGHT}' 100, '${GSFAxes.WIDTH}' 100, '${GSFAxes.ROUND}' 100", + "'${GSFAxes.WEIGHT.tag}' 100, '${GSFAxes.WIDTH.tag}' 100, '${GSFAxes.ROUND.tag}' 100", initFvar, ) val updatedFvar = @@ -32,7 +32,8 @@ class FontVariationUtilsTest : SysuiTestCase() { roundness = 100, ) Assert.assertEquals( - "'${GSFAxes.WEIGHT}' 200, '${GSFAxes.WIDTH}' 100, '${GSFAxes.OPTICAL_SIZE}' 0, '${GSFAxes.ROUND}' 100", + "'${GSFAxes.WEIGHT.tag}' 200, '${GSFAxes.WIDTH.tag}' 100," + + " '${GSFAxes.OPTICAL_SIZE.tag}' 0, '${GSFAxes.ROUND.tag}' 100", updatedFvar, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt index 4e14fec8408f..943ada9346e7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt @@ -18,6 +18,9 @@ package com.android.systemui.media.controls.ui.binder import android.animation.Animator import android.animation.ObjectAnimator +import android.icu.text.MeasureFormat +import android.icu.util.Measure +import android.icu.util.MeasureUnit import android.testing.TestableLooper import android.view.View import android.widget.SeekBar @@ -30,6 +33,7 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat +import java.util.Locale import org.junit.Before import org.junit.Rule import org.junit.Test @@ -61,11 +65,11 @@ class SeekBarObserverTest : SysuiTestCase() { fun setUp() { context.orCreateTestableResources.addOverride( R.dimen.qs_media_enabled_seekbar_height, - enabledHeight + enabledHeight, ) context.orCreateTestableResources.addOverride( R.dimen.qs_media_disabled_seekbar_height, - disabledHeight + disabledHeight, ) seekBarView = SeekBar(context) @@ -110,14 +114,31 @@ class SeekBarObserverTest : SysuiTestCase() { @Test fun seekBarProgress() { + val elapsedTime = 3000 + val duration = (1.5 * 60 * 60 * 1000).toInt() // WHEN part of the track has been played - val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true) + val data = SeekBarViewModel.Progress(true, true, true, false, elapsedTime, duration, true) observer.onChanged(data) // THEN seek bar shows the progress - assertThat(seekBarView.progress).isEqualTo(3000) - assertThat(seekBarView.max).isEqualTo(120000) - - val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00") + assertThat(seekBarView.progress).isEqualTo(elapsedTime) + assertThat(seekBarView.max).isEqualTo(duration) + + val expectedProgress = + MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) + .formatMeasures(Measure(3, MeasureUnit.SECOND)) + val expectedDuration = + MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) + .formatMeasures( + Measure(1, MeasureUnit.HOUR), + Measure(30, MeasureUnit.MINUTE), + Measure(0, MeasureUnit.SECOND), + ) + val desc = + context.getString( + R.string.controls_media_seekbar_description, + expectedProgress, + expectedDuration, + ) assertThat(seekBarView.contentDescription).isEqualTo(desc) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt index e38ea30daf0e..9fd189f35c32 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt @@ -20,11 +20,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel -import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runTest @@ -33,22 +36,20 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class CallChipInteractorTest : SysuiTestCase() { - val kosmos = Kosmos() + val kosmos = testKosmos().useUnconfinedTestDispatcher() val repo = kosmos.ongoingCallRepository val underTest = kosmos.callChipInteractor @Test - fun ongoingCallState_matchesRepo() = - kosmos.testScope.runTest { + fun ongoingCallState_matchesState() = + kosmos.runTest { val latest by collectLastValue(underTest.ongoingCallState) - val inCall = inCallModel(startTimeMs = 1000) - repo.setOngoingCallState(inCall) - assertThat(latest).isEqualTo(inCall) + addOngoingCallState(key = "testKey") + assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) - val noCall = OngoingCallModel.NoCall - repo.setOngoingCallState(noCall) - assertThat(latest).isEqualTo(noCall) + removeOngoingCallState(key = "testKey") + assertThat(latest).isEqualTo(OngoingCallModel.NoCall) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt index e044d1db92a9..fda4ab005446 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt @@ -27,7 +27,9 @@ import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.plugins.activityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView @@ -35,17 +37,11 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.core.StatusBarConnectedDisplays -import com.android.systemui.statusbar.notification.data.model.activeNotificationModel -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore -import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel -import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization -import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository -import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel -import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat @@ -60,10 +56,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class CallChipViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val notificationListRepository = kosmos.activeNotificationListRepository - private val testScope = kosmos.testScope - private val repo = kosmos.ongoingCallRepository + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val chipBackgroundView = mock<ChipBackgroundContainer>() private val chipView = @@ -82,53 +75,53 @@ class CallChipViewModelTest : SysuiTestCase() { @Test fun chip_noCall_isHidden() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(OngoingCallModel.NoCall) + removeOngoingCallState("testKey") assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) } @Test fun chip_inCall_zeroStartTime_isShownAsIconOnly() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 0)) + addOngoingCallState(startTimeMs = 0) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) } @Test fun chip_inCall_negativeStartTime_isShownAsIconOnly() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = -2)) + addOngoingCallState(startTimeMs = -2) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) } @Test fun chip_inCall_positiveStartTime_isShownAsTimer() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 345)) + addOngoingCallState(startTimeMs = 345) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java) } @Test fun chip_inCall_startTimeConvertedToElapsedRealtime() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) kosmos.fakeSystemClock.setCurrentTimeMillis(3000) kosmos.fakeSystemClock.setElapsedRealtime(400_000) - repo.setOngoingCallState(inCallModel(startTimeMs = 1000)) + addOngoingCallState(startTimeMs = 1000) // The OngoingCallModel start time is relative to currentTimeMillis, so this call // started 2000ms ago (1000 - 3000). The OngoingActivityChipModel start time needs to be @@ -141,13 +134,11 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun chip_positiveStartTime_connectedDisplaysFlagOn_iconIsNotifIcon() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) val notifKey = "testNotifKey" - repo.setOngoingCallState( - inCallModel(startTimeMs = 1000, notificationIcon = null, notificationKey = notifKey) - ) + addOngoingCallState(startTimeMs = 1000, statusBarChipIconView = null, key = notifKey) assertThat((latest as OngoingActivityChipModel.Active).icon) .isInstanceOf( @@ -163,16 +154,14 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon_withContentDescription() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) val notifIcon = createStatusBarIconViewOrNull() - repo.setOngoingCallState( - inCallModel( - startTimeMs = 0, - notificationIcon = notifIcon, - appName = "Fake app name", - ) + addOngoingCallState( + startTimeMs = 0, + statusBarChipIconView = notifIcon, + appName = "Fake app name", ) assertThat((latest as OngoingActivityChipModel.Active).icon) @@ -190,16 +179,13 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState( - inCallModel( - startTimeMs = 0, - notificationIcon = createStatusBarIconViewOrNull(), - notificationKey = "notifKey", - appName = "Fake app name", - ) + addOngoingCallState( + key = "notifKey", + statusBarChipIconView = createStatusBarIconViewOrNull(), + appName = "Fake app name", ) assertThat((latest as OngoingActivityChipModel.Active).icon) @@ -219,10 +205,10 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOff_iconIsPhone() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = null)) + addOngoingCallState(statusBarChipIconView = null) assertThat((latest as OngoingActivityChipModel.Active).icon) .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java) @@ -237,16 +223,13 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState( - inCallModel( - startTimeMs = 1000, - notificationIcon = null, - notificationKey = "notifKey", - appName = "Fake app name", - ) + addOngoingCallState( + key = "notifKey", + statusBarChipIconView = null, + appName = "Fake app name", ) assertThat((latest as OngoingActivityChipModel.Active).icon) @@ -265,10 +248,10 @@ class CallChipViewModelTest : SysuiTestCase() { @Test fun chip_positiveStartTime_colorsAreAccentThemed() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 1000, promotedContent = null)) + addOngoingCallState(startTimeMs = 1000, promotedContent = null) assertThat((latest as OngoingActivityChipModel.Active).colors) .isEqualTo(ColorsModel.AccentThemed) @@ -276,10 +259,10 @@ class CallChipViewModelTest : SysuiTestCase() { @Test fun chip_zeroStartTime_colorsAreAccentThemed() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 0, promotedContent = null)) + addOngoingCallState(startTimeMs = 0, promotedContent = null) assertThat((latest as OngoingActivityChipModel.Active).colors) .isEqualTo(ColorsModel.AccentThemed) @@ -287,19 +270,19 @@ class CallChipViewModelTest : SysuiTestCase() { @Test fun chip_resetsCorrectly() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) kosmos.fakeSystemClock.setCurrentTimeMillis(3000) kosmos.fakeSystemClock.setElapsedRealtime(400_000) // Start a call - repo.setOngoingCallState(inCallModel(startTimeMs = 1000)) + addOngoingCallState(key = "testKey", startTimeMs = 1000) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs) .isEqualTo(398_000) // End the call - repo.setOngoingCallState(OngoingCallModel.NoCall) + removeOngoingCallState(key = "testKey") assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) // Let 100_000ms elapse @@ -307,7 +290,7 @@ class CallChipViewModelTest : SysuiTestCase() { kosmos.fakeSystemClock.setElapsedRealtime(500_000) // Start a new call, which started 1000ms ago - repo.setOngoingCallState(inCallModel(startTimeMs = 102_000)) + addOngoingCallState(key = "testKey", startTimeMs = 102_000) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs) .isEqualTo(499_000) @@ -316,10 +299,10 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @DisableChipsModernization fun chip_inCall_nullIntent_nullClickListener() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null)) + addOngoingCallState(contentIntent = null) assertThat((latest as OngoingActivityChipModel.Active).onClickListenerLegacy).isNull() } @@ -327,11 +310,11 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @DisableChipsModernization fun chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntent() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) val pendingIntent = mock<PendingIntent>() - repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = pendingIntent)) + addOngoingCallState(startTimeMs = 1000, contentIntent = pendingIntent) val clickListener = (latest as OngoingActivityChipModel.Active).onClickListenerLegacy assertThat(clickListener).isNotNull() @@ -345,11 +328,11 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @DisableChipsModernization fun chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntent() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) val pendingIntent = mock<PendingIntent>() - repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = pendingIntent)) + addOngoingCallState(startTimeMs = 0, contentIntent = pendingIntent) val clickListener = (latest as OngoingActivityChipModel.Active).onClickListenerLegacy assertThat(clickListener).isNotNull() @@ -364,14 +347,10 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @EnableChipsModernization fun chip_inCall_nullIntent_noneClickBehavior() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) - postOngoingCallNotification( - repository = notificationListRepository, - startTimeMs = 1000L, - intent = null, - ) + addOngoingCallState(startTimeMs = 1000, contentIntent = null) assertThat((latest as OngoingActivityChipModel.Active).clickBehavior) .isInstanceOf(OngoingActivityChipModel.ClickBehavior.None::class.java) @@ -380,15 +359,11 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @EnableChipsModernization fun chip_inCall_positiveStartTime_validIntent_clickBehaviorLaunchesIntent() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) val pendingIntent = mock<PendingIntent>() - postOngoingCallNotification( - repository = notificationListRepository, - startTimeMs = 1000L, - intent = pendingIntent, - ) + addOngoingCallState(startTimeMs = 1000, contentIntent = pendingIntent) val clickBehavior = (latest as OngoingActivityChipModel.Active).clickBehavior assertThat(clickBehavior) @@ -405,15 +380,11 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @EnableChipsModernization fun chip_inCall_zeroStartTime_validIntent_clickBehaviorLaunchesIntent() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.chip) val pendingIntent = mock<PendingIntent>() - postOngoingCallNotification( - repository = notificationListRepository, - startTimeMs = 0L, - intent = pendingIntent, - ) + addOngoingCallState(startTimeMs = 0, contentIntent = pendingIntent) val clickBehavior = (latest as OngoingActivityChipModel.Active).clickBehavior assertThat(clickBehavior) @@ -435,27 +406,6 @@ class CallChipViewModelTest : SysuiTestCase() { mock<StatusBarIconView>() } - fun postOngoingCallNotification( - repository: ActiveNotificationListRepository, - startTimeMs: Long, - intent: PendingIntent?, - ) { - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = startTimeMs, - callType = CallType.Ongoing, - statusBarChipIcon = null, - contentIntent = intent, - ) - ) - } - .build() - } - private val PROMOTED_CONTENT_WITH_COLOR = PromotedNotificationContentModel.Builder("notif") .apply { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index 626dcd5b0864..719924c865fd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -22,15 +22,18 @@ import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask @@ -48,16 +51,13 @@ import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization -import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository -import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel -import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -72,15 +72,14 @@ import org.mockito.kotlin.whenever /** Tests for [OngoingActivityChipsViewModel] when the [StatusBarNotifChips] flag is disabled. */ @SmallTest @RunWith(AndroidJUnit4::class) +@EnableFlags(StatusBarChipsModernization.FLAG_NAME) @DisableFlags(StatusBarNotifChips.FLAG_NAME) class OngoingActivityChipsViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val systemClock = kosmos.fakeSystemClock private val screenRecordState = kosmos.screenRecordRepository.screenRecordState private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState - private val callRepo = kosmos.ongoingCallRepository private val mockSystemUIDialog = mock<SystemUIDialog>() private val chipBackgroundView = mock<ChipBackgroundContainer>() @@ -96,7 +95,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { private val mockExpandable: Expandable = mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } - private val underTest = kosmos.ongoingActivityChipsViewModel + private val Kosmos.underTest by Kosmos.Fixture { ongoingActivityChipsViewModel } @Before fun setUp() { @@ -111,10 +110,10 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_allHidden_hidden() = - testScope.runTest { + kosmos.runTest { screenRecordState.value = ScreenRecordModel.DoingNothing mediaProjectionState.value = MediaProjectionState.NotProjecting - callRepo.setOngoingCallState(OngoingCallModel.NoCall) + removeOngoingCallState("testKey") val latest by collectLastValue(underTest.primaryChip) @@ -123,10 +122,10 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_screenRecordShow_restHidden_screenRecordShown() = - testScope.runTest { + kosmos.runTest { screenRecordState.value = ScreenRecordModel.Recording mediaProjectionState.value = MediaProjectionState.NotProjecting - callRepo.setOngoingCallState(OngoingCallModel.NoCall) + removeOngoingCallState("testKey") val latest by collectLastValue(underTest.primaryChip) @@ -135,10 +134,10 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_screenRecordShowAndCallShow_screenRecordShown() = - testScope.runTest { + kosmos.runTest { screenRecordState.value = ScreenRecordModel.Recording - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + addOngoingCallState() val latest by collectLastValue(underTest.primaryChip) @@ -147,11 +146,11 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_screenRecordShowAndShareToAppShow_screenRecordShown() = - testScope.runTest { + kosmos.runTest { screenRecordState.value = ScreenRecordModel.Recording mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - callRepo.setOngoingCallState(OngoingCallModel.NoCall) + removeOngoingCallState("testKey") val latest by collectLastValue(underTest.primaryChip) @@ -160,11 +159,11 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_shareToAppShowAndCallShow_shareToAppShown() = - testScope.runTest { + kosmos.runTest { screenRecordState.value = ScreenRecordModel.DoingNothing mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + addOngoingCallState() val latest by collectLastValue(underTest.primaryChip) @@ -173,15 +172,13 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_screenRecordAndShareToAppAndCastToOtherHideAndCallShown_callShown() = - testScope.runTest { + kosmos.runTest { val notificationKey = "call" screenRecordState.value = ScreenRecordModel.DoingNothing // MediaProjection covers both share-to-app and cast-to-other-device mediaProjectionState.value = MediaProjectionState.NotProjecting - callRepo.setOngoingCallState( - inCallModel(startTimeMs = 34, notificationKey = notificationKey) - ) + addOngoingCallState(key = notificationKey) val latest by collectLastValue(underTest.primaryChip) @@ -190,12 +187,10 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() = - testScope.runTest { + kosmos.runTest { // Start with just the lowest priority chip shown val callNotificationKey = "call" - callRepo.setOngoingCallState( - inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) - ) + addOngoingCallState(key = callNotificationKey) // And everything else hidden mediaProjectionState.value = MediaProjectionState.NotProjecting screenRecordState.value = ScreenRecordModel.DoingNothing @@ -224,15 +219,13 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_highestPriorityChipRemoved_showsNextPriorityChip() = - testScope.runTest { + kosmos.runTest { // WHEN all chips are active screenRecordState.value = ScreenRecordModel.Recording mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) val callNotificationKey = "call" - callRepo.setOngoingCallState( - inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) - ) + addOngoingCallState(key = callNotificationKey) val latest by collectLastValue(underTest.primaryChip) @@ -255,17 +248,15 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { /** Regression test for b/347726238. */ @Test fun primaryChip_timerDoesNotResetAfterSubscribersRestart() = - testScope.runTest { + kosmos.runTest { var latest: OngoingActivityChipModel? = null - val job1 = underTest.primaryChip.onEach { latest = it }.launchIn(this) + val job1 = underTest.primaryChip.onEach { latest = it }.launchIn(kosmos.testScope) // Start a chip with a timer systemClock.setElapsedRealtime(1234) screenRecordState.value = ScreenRecordModel.Recording - runCurrent() - assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs) .isEqualTo(1234) @@ -276,9 +267,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { systemClock.setElapsedRealtime(5678) // WHEN we re-subscribe to the chip flow - val job2 = underTest.primaryChip.onEach { latest = it }.launchIn(this) - - runCurrent() + val job2 = underTest.primaryChip.onEach { latest = it }.launchIn(kosmos.testScope) // THEN the old start time is still used assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs) @@ -289,14 +278,14 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() = - testScope.runTest { + kosmos.runTest { screenRecordState.value = ScreenRecordModel.Recording mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen( NORMAL_PACKAGE, hostDeviceName = "Recording Display", ) - callRepo.setOngoingCallState(OngoingCallModel.NoCall) + removeOngoingCallState("testKey") val latest by collectLastValue(underTest.primaryChip) @@ -319,11 +308,11 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_projectionStoppedViaDialog_chipHiddenWithoutAnimation() = - testScope.runTest { + kosmos.runTest { mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) screenRecordState.value = ScreenRecordModel.DoingNothing - callRepo.setOngoingCallState(OngoingCallModel.NoCall) + removeOngoingCallState("testKey") val latest by collectLastValue(underTest.primaryChip) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt index ab475c5edb76..2f6bedb42e45 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt @@ -37,6 +37,8 @@ import com.android.systemui.statusbar.layout.LetterboxAppearance import com.android.systemui.statusbar.layout.LetterboxAppearanceCalculator import com.android.systemui.statusbar.layout.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent +import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization +import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel @@ -387,6 +389,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { } @Test + @DisableChipsModernization fun statusBarMode_ongoingCallAndFullscreen_semiTransparent() = testScope.runTest { val latest by collectLastValue(underTest.statusBarAppearance) @@ -398,6 +401,19 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { } @Test + @EnableChipsModernization + fun statusBarMode_ongoingProcessRequiresStatusBarVisible_andFullscreen_semiTransparent() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + underTest.setOngoingProcessRequiresStatusBarVisible(true) + onSystemBarAttributesChanged(requestedVisibleTypes = WindowInsets.Type.navigationBars()) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT) + } + + @Test + @DisableChipsModernization fun statusBarMode_ongoingCallButNotFullscreen_matchesAppearance() = testScope.runTest { val latest by collectLastValue(underTest.statusBarAppearance) @@ -413,6 +429,23 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { } @Test + @EnableChipsModernization + fun statusBarMode_ongoingProcessRequiresStatusBarVisible_butNotFullscreen_matchesAppearance() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + underTest.setOngoingProcessRequiresStatusBarVisible(true) + + onSystemBarAttributesChanged( + requestedVisibleTypes = WindowInsets.Type.statusBars(), + appearance = APPEARANCE_OPAQUE_STATUS_BARS, + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE) + } + + @Test + @DisableChipsModernization fun statusBarMode_fullscreenButNotOngoingCall_matchesAppearance() = testScope.runTest { val latest by collectLastValue(underTest.statusBarAppearance) @@ -427,6 +460,21 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { } @Test + @EnableChipsModernization + fun statusBarMode_fullscreen_butNotOngoingProcessRequiresStatusBarVisible_matchesAppearance() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + underTest.setOngoingProcessRequiresStatusBarVisible(false) + onSystemBarAttributesChanged( + requestedVisibleTypes = WindowInsets.Type.navigationBars(), + appearance = APPEARANCE_OPAQUE_STATUS_BARS, + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE) + } + + @Test fun statusBarMode_transientShown_semiTransparent() = testScope.runTest { val latest by collectLastValue(underTest.statusBarAppearance) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt index a1772e3f62ed..510167d10db4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt @@ -10,6 +10,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.kosmos.testScope import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository @@ -165,7 +166,10 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { .setSummary(summary) .addChild(notification.entry) .build() - assertSame(summary, notification.entry.parent?.summary) + + val parentSummary = if (notification.entry.parent is GroupEntry) + (notification.entry.parent as GroupEntry).summary else null + assertSame(summary, parentSummary) `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(false) `when`(headsUpManager.isHeadsUpEntry(summary.key)).thenReturn(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt index 426af264da07..83e26c4220b1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection +import android.app.Notification +import android.graphics.Color import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.TestableLooper.RunWithLooper @@ -72,4 +74,35 @@ class BundleEntryTest : SysuiTestCase() { fun getKey_adapter() { assertThat(underTest.entryAdapter.key).isEqualTo("key") } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isClearable_adapter() { + assertThat(underTest.entryAdapter.isClearable).isTrue() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getSummarization_adapter() { + assertThat(underTest.entryAdapter.summarization).isNull() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getContrastedColor_adapter() { + assertThat(underTest.entryAdapter.getContrastedColor(context, false, Color.WHITE)) + .isEqualTo(Notification.COLOR_DEFAULT) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun canPeek_adapter() { + assertThat(underTest.entryAdapter.canPeek()).isFalse() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getWhen_adapter() { + assertThat(underTest.entryAdapter.`when`).isEqualTo(0) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 19d1224a9bf3..1f5c6722f38e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -163,9 +163,10 @@ public class NotificationEntryTest extends SysuiTestCase { @Test public void testIsExemptFromDndVisualSuppression_media() { + MediaSession session = new MediaSession(mContext, "test"); Notification.Builder n = new Notification.Builder(mContext, "") .setStyle(new Notification.MediaStyle() - .setMediaSession(mock(MediaSession.Token.class))) + .setMediaSession(session.getSessionToken())) .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") .setContentText("Text"); @@ -593,6 +594,76 @@ public class NotificationEntryTest extends SysuiTestCase { assertThat(entry.getEntryAdapter().getGroupRoot()).isEqualTo(parent.getEntryAdapter()); } + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + public void isClearable_adapter() { + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + Notification notification = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .build(); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setChannel(mChannel) + .setId(mId++) + .setNotification(notification) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + entry.setRow(row); + + assertThat(entry.getEntryAdapter().isClearable()).isEqualTo(entry.isClearable()); + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + public void getSummarization_adapter() { + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + Notification notification = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .build(); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setChannel(mChannel) + .setId(mId++) + .setNotification(notification) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + Ranking ranking = new RankingBuilder(entry.getRanking()) + .setSummarization("hello") + .build(); + entry.setRanking(ranking); + entry.setRow(row); + + assertThat(entry.getEntryAdapter().getSummarization()).isEqualTo("hello"); + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + public void getIcons_adapter() { + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + Notification notification = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .build(); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setChannel(mChannel) + .setId(mId++) + .setNotification(notification) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + entry.setRow(row); + + assertThat(entry.getEntryAdapter().getIcons()).isEqualTo(entry.getIcons()); + } + private Notification.Action createContextualAction(String title) { return new Notification.Action.Builder( Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt index 7781df1ad91f..43cb9575b609 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt @@ -16,8 +16,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification +import android.app.Notification.MediaStyle +import android.media.session.MediaSession import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings +import android.service.notification.StatusBarNotification import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.dumpManager @@ -36,6 +39,7 @@ import com.android.systemui.scene.data.repository.Idle import com.android.systemui.scene.data.repository.setTransition import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -217,6 +221,16 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu mock<ExpandableNotificationRow>().apply { whenever(isMediaRow).thenReturn(true) } + sbn = SbnBuilder().setNotification( + Notification.Builder(context, "channel").setStyle( + MediaStyle().setMediaSession( + MediaSession( + context, + "tag" + ).sessionToken + ) + ).build() + ).build() } collectionListener.onEntryAdded(fakeEntry) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index 99bd4fc549a8..d9c91771043b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.getAttachState import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner @@ -409,7 +410,7 @@ private fun buildSection( ): NotifSection { return NotifSection(object : NotifSectioner("Section $index (bucket=$bucket)", bucket) { - override fun isInSection(entry: ListEntry?): Boolean { + override fun isInSection(entry: PipelineEntry?): Boolean { throw NotImplementedError("This should never be called") } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt index e6fbc725af04..4a05804f663a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.ShadeListBuilder import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener @@ -40,6 +41,7 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions +import java.nio.channels.Pipe @SmallTest @RunWith(AndroidJUnit4::class) @@ -198,7 +200,7 @@ class RenderStageManagerTest : SysuiTestCase() { ) private class FakeNotifViewRenderer : NotifViewRenderer { - override fun onRenderList(notifList: List<ListEntry>) {} + override fun onRenderList(notifList: List<PipelineEntry>) {} override fun getGroupController(group: GroupEntry): NotifGroupController = mock() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt index 4c1f4f17e00c..1b8d64d5483c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt @@ -80,9 +80,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.testKosmos import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.wmshell.BubblesManager -import java.util.Optional -import kotlin.test.assertNotNull -import kotlin.test.fail import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -110,6 +107,9 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters +import java.util.Optional +import kotlin.test.assertNotNull +import kotlin.test.fail /** Tests for [NotificationGutsManager]. */ @SmallTest @@ -509,7 +509,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( .setImportance(NotificationManager.IMPORTANCE_HIGH) .build() - whenever(row.isNonblockable).thenReturn(false) whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) val statusBarNotification = entry.sbn gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -546,7 +545,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.isNonblockable).thenReturn(false) val statusBarNotification = row.entry.sbn val entry = row.entry @@ -586,7 +584,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.isNonblockable).thenReturn(false) val statusBarNotification = row.entry.sbn val entry = row.entry @@ -641,7 +638,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( ): NotificationMenuRowPlugin.MenuItem { val menuRow: NotificationMenuRowPlugin = NotificationMenuRow(mContext, peopleNotificationIdentifier) - menuRow.createMenu(row, row.entry.sbn) + menuRow.createMenu(row) val menuItem = menuRow.getLongpressMenuItem(mContext) assertNotNull(menuItem) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index 027e899e20df..9fdfca14a5b2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -72,7 +72,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { public void testAttachDetach() { NotificationMenuRowPlugin row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewUtils.attachView(row.getMenuView()); TestableLooper.get(this).processAllMessages(); ViewUtils.detachView(row.getMenuView()); @@ -83,9 +83,9 @@ public class NotificationMenuRowTest extends LeakCheckedTest { public void testRecreateMenu() { NotificationMenuRowPlugin row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); assertTrue(row.getMenuView() != null); - row.createMenu(mRow, null); + row.createMenu(mRow); assertTrue(row.getMenuView() != null); } @@ -103,7 +103,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0); NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewGroup container = (ViewGroup) row.getMenuView(); // noti blocking @@ -116,7 +116,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0); NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewGroup container = (ViewGroup) row.getMenuView(); // just for noti blocking @@ -129,7 +129,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0); NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewGroup container = (ViewGroup) row.getMenuView(); // one for snooze and one for noti blocking @@ -142,7 +142,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 1); NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewGroup container = (ViewGroup) row.getMenuView(); // Clear menu @@ -417,7 +417,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { public void testOnTouchMove() { NotificationMenuRow row = Mockito.spy( new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); - row.createMenu(mRow, null); + row.createMenu(mRow); doReturn(50f).when(row).getDismissThreshold(); doReturn(true).when(row).canBeDismissed(); doReturn(mView).when(row).getMenuView(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt index 9a6a6997b96f..081f52c4ff3b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt @@ -135,6 +135,7 @@ class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() { .thenReturn(mock()) whenever(requireViewById<View>(R.id.app_name_text)).thenReturn(mock()) whenever(requireViewById<View>(R.id.conversation_text)).thenReturn(mock()) + whenever(requireViewById<View>(R.id.title)).thenReturn(mock()) } return mockView } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt index e5cb0fbc9e4b..885e71e7a7fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.testKosmos import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -49,7 +50,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { private val sectionsManager = mock<NotificationSectionsManager>() private val msdlPlayer = kosmos.fakeMSDLPlayer private var canRowBeDismissed = true - private var magneticAnimationsCancelled = false + private var magneticAnimationsCancelled = MutableList(childrenNumber) { false } private val underTest = kosmos.magneticNotificationRowManagerImpl @@ -64,8 +65,10 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { NotificationTestHelper(mContext, mDependency, kosmos.testableLooper, featureFlags) children = notificationTestHelper.createGroup(childrenNumber).childrenContainer swipedRow = children.attachedChildren[childrenNumber / 2] - configureMagneticRowListener(swipedRow) - magneticAnimationsCancelled = false + children.attachedChildren.forEachIndexed { index, row -> + row.magneticRowListener = row.magneticRowListener.asTestableListener(index) + } + magneticAnimationsCancelled.replaceAll { false } } @Test @@ -259,14 +262,14 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { underTest.onMagneticInteractionEnd(swipedRow, velocity = null) // THEN magnetic animations are cancelled - assertThat(magneticAnimationsCancelled).isTrue() + assertThat(magneticAnimationsCancelled[childrenNumber / 2]).isTrue() } @Test fun onMagneticInteractionEnd_forMagneticNeighbor_cancelsMagneticAnimations() = kosmos.testScope.runTest { - val neighborRow = children.attachedChildren[childrenNumber / 2 - 1] - configureMagneticRowListener(neighborRow) + val neighborIndex = childrenNumber / 2 - 1 + val neighborRow = children.attachedChildren[neighborIndex] // GIVEN that targets are set setTargets() @@ -275,9 +278,15 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { underTest.onMagneticInteractionEnd(neighborRow, null) // THEN magnetic animations are cancelled - assertThat(magneticAnimationsCancelled).isTrue() + assertThat(magneticAnimationsCancelled[neighborIndex]).isTrue() } + @After + fun tearDown() { + // We reset the manager so that all MagneticRowListener can cancel all animations + underTest.reset() + } + private fun setDetachedState() { val threshold = 100f underTest.setSwipeThresholdPx(threshold) @@ -302,27 +311,33 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { originalTranslation * MagneticNotificationRowManagerImpl.MAGNETIC_REDUCTION - private fun configureMagneticRowListener(row: ExpandableNotificationRow) { - val listener = - object : MagneticRowListener { - override fun setMagneticTranslation(translation: Float) { - row.translation = translation - } + private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener { + val delegate = this + return object : MagneticRowListener { + override fun setMagneticTranslation(translation: Float) { + delegate.setMagneticTranslation(translation) + } - override fun triggerMagneticForce( - endTranslation: Float, - springForce: SpringForce, - startVelocity: Float, - ) {} + override fun triggerMagneticForce( + endTranslation: Float, + springForce: SpringForce, + startVelocity: Float, + ) { + delegate.triggerMagneticForce(endTranslation, springForce, startVelocity) + } - override fun cancelMagneticAnimations() { - magneticAnimationsCancelled = true - } + override fun cancelMagneticAnimations() { + magneticAnimationsCancelled[rowIndex] = true + delegate.cancelMagneticAnimations() + } - override fun cancelTranslationAnimations() {} + override fun cancelTranslationAnimations() { + delegate.cancelTranslationAnimations() + } - override fun canRowBeDismissed(): Boolean = canRowBeDismissed + override fun canRowBeDismissed(): Boolean { + return canRowBeDismissed } - row.magneticRowListener = listener + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 01ba4df3a314..7603eecd27ff 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -146,6 +146,20 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @EnableSceneContainer + fun resetViewStates_defaultHun_hasShadow() { + val headsUpTop = 200f + ambientState.headsUpTop = headsUpTop + + whenever(notificationRow.isPinned).thenReturn(true) + whenever(notificationRow.isHeadsUp).thenReturn(true) + + stackScrollAlgorithm.resetViewStates(ambientState, 0) + + assertThat(notificationRow.viewState.zTranslation).isGreaterThan(baseZ) + } + + @Test @DisableSceneContainer fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() { whenever(notificationRow.isPinned).thenReturn(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 21297e3e64b7..c630a1c1e006 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -17,14 +17,15 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest -import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository -import com.android.systemui.communal.data.repository.communalSceneRepository +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues @@ -54,11 +55,14 @@ import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.data.repository.Idle import com.android.systemui.scene.data.repository.Transition import com.android.systemui.scene.data.repository.setTransition +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.shade.domain.interactor.enableSingleShade @@ -125,7 +129,6 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S kosmos.sharedNotificationContainerInteractor } private val largeScreenHeaderHelper by lazy { kosmos.mockLargeScreenHeaderHelper } - private val communalSceneRepository by lazy { kosmos.communalSceneRepository } lateinit var underTest: SharedNotificationContainerViewModel @@ -591,6 +594,25 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S } @Test + @DisableSceneContainer + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun isOnLockscreenFalseWhenCommunalShowing() = + kosmos.runTest { + val isOnLockscreen by collectLastValue(underTest.isOnLockscreen) + + setTransition( + sceneTransition = Idle(Scenes.Bouncer), + stateTransition = TransitionStep(from = LOCKSCREEN, to = PRIMARY_BOUNCER), + ) + assertThat(isOnLockscreen).isTrue() + + testScope.showCommunalScene() + + // If bouncer is showing over the hub, it should not be considered on lockscreen + assertThat(isOnLockscreen).isFalse() + } + + @Test fun isOnLockscreenWithoutShade() = testScope.runTest { val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade) @@ -1472,20 +1494,24 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S } private fun TestScope.showCommunalScene() { - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) - ) - communalSceneRepository.setTransitionState(transitionState) + val targetScene = + if (SceneContainerFlag.isEnabled) { + Scenes.Communal + } else { + CommunalScenes.Communal + } + kosmos.communalSceneInteractor.changeScene(targetScene, "test") runCurrent() } private fun TestScope.hideCommunalScene() { - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Blank) - ) - communalSceneRepository.setTransitionState(transitionState) + val targetScene = + if (SceneContainerFlag.isEnabled) { + Scenes.Lockscreen + } else { + CommunalScenes.Blank + } + kosmos.communalSceneInteractor.changeScene(targetScene, "test") runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index d17abd7da05c..6066a3870dfe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.os.ParcelUuid +import android.platform.test.annotations.EnableFlags import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET @@ -33,9 +34,12 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.core.NewStatusBarIcons +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryLogbufferName import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot @@ -897,6 +901,71 @@ class MobileIconsInteractorTest : SysuiTestCase() { assertThat(latest).isEqualTo(2) } + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun isStackable_tracksNumberOfSubscriptions() = + kosmos.runTest { + val latest by collectLastValue(underTest.isStackable) + + connectionsRepository.setSubscriptions(listOf(SUB_1)) + assertThat(latest).isFalse() + + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + assertThat(latest).isTrue() + + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2, SUB_3_OPP)) + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun isStackable_checksForTerrestrialConnections() = + kosmos.runTest { + val exclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = true, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + + val latest by collectLastValue(underTest.isStackable) + + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + assertThat(latest).isTrue() + + connectionsRepository.setSubscriptions(listOf(SUB_1, exclusivelyNonTerrestrialSub)) + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun isStackable_checksForNumberOfBars() = + kosmos.runTest { + val latest by collectLastValue(underTest.isStackable) + + // Number of levels is the same for both + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + setNumberOfLevelsForSubId(SUB_1_ID, 5) + setNumberOfLevelsForSubId(SUB_2_ID, 5) + + assertThat(latest).isTrue() + + // Change the number of levels to be different than SUB_2 + setNumberOfLevelsForSubId(SUB_1_ID, 6) + + assertThat(latest).isFalse() + } + + private fun setNumberOfLevelsForSubId(subId: Int, numberOfLevels: Int) { + with(kosmos) { + (fakeMobileConnectionsRepository.getRepoForSubId(subId) + as FakeMobileConnectionRepository) + .numberOfLevels + .value = numberOfLevels + } + } + /** * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions * flow. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt new file mode 100644 index 000000000000..20bdebd069f7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt @@ -0,0 +1,145 @@ +/* + * 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.statusbar.pipeline.mobile.ui.viewmodel + +import android.platform.test.annotations.EnableFlags +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.statusbar.core.NewStatusBarIcons +import com.android.systemui.statusbar.core.StatusBarRootModernization +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class StackedMobileIconViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val testScope = kosmos.testScope + + private val Kosmos.underTest: StackedMobileIconViewModel by Fixture { + stackedMobileIconViewModel + } + + @Before + fun setUp() { + kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false) + kosmos.underTest.activateIn(testScope) + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun dualSim_filtersOutNonDualConnections() = + kosmos.runTest { + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf() + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3) + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + assertThat(underTest.dualSim).isNotNull() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun dualSim_filtersOutNonCellularIcons() = + kosmos.runTest { + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor + .getInteractorForSubId(SUB_1.subscriptionId)!! + .signalLevelIcon + .value = + SignalIconModel.Satellite( + level = 0, + icon = Icon.Resource(res = 0, contentDescription = null), + ) + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + assertThat(underTest.dualSim).isNull() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun dualSim_tracksActiveSubId() = + kosmos.runTest { + // Active sub id is null, order is unchanged + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + setIconLevel(SUB_1.subscriptionId, 1) + setIconLevel(SUB_2.subscriptionId, 2) + + assertThat(underTest.dualSim!!.primary.level).isEqualTo(1) + assertThat(underTest.dualSim!!.secondary.level).isEqualTo(2) + + // Active sub is 2, order is swapped + fakeMobileIconsInteractor.activeMobileDataSubscriptionId.value = SUB_2.subscriptionId + + assertThat(underTest.dualSim!!.primary.level).isEqualTo(2) + assertThat(underTest.dualSim!!.secondary.level).isEqualTo(1) + } + + private fun setIconLevel(subId: Int, level: Int) { + with(kosmos.fakeMobileIconsInteractor.getInteractorForSubId(subId)!!) { + signalLevelIcon.value = + (signalLevelIcon.value as SignalIconModel.Cellular).copy(level = level) + } + } + + companion object { + private val SUB_1 = + SubscriptionModel( + subscriptionId = 1, + isOpportunistic = false, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, + ) + private val SUB_2 = + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = false, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, + ) + private val SUB_3 = + SubscriptionModel( + subscriptionId = 3, + isOpportunistic = false, + carrierName = "Carrier 3", + profileClass = PROFILE_CLASS_UNSET, + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt index eb961bd5f4ae..f91e3a612862 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt @@ -64,6 +64,8 @@ class FakeHomeStatusBarViewModel( override val isHomeStatusBarAllowedByScene = MutableStateFlow(false) + override val canShowOngoingActivityChips: Flow<Boolean> = MutableStateFlow(false) + override val batteryViewModelFactory: BatteryViewModel.Factory = object : BatteryViewModel.Factory { override fun create(): BatteryViewModel = mock(BatteryViewModel::class.java) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt index 51484baf1b7b..8a796fc33608 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt @@ -678,6 +678,60 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { } @Test + fun canShowOngoingActivityChips_statusBarHidden_noSecureCamera_noHun_false() = + kosmos.runTest { + val latest by collectLastValue(underTest.canShowOngoingActivityChips) + + // home status bar not allowed + kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null) + + assertThat(latest).isFalse() + } + + @Test + fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_noHun_true() = + kosmos.runTest { + val latest by collectLastValue(underTest.canShowOngoingActivityChips) + + transitionKeyguardToGone() + + assertThat(latest).isTrue() + } + + @Test + fun canShowOngoingActivityChips_statusBarNotHidden_secureCamera_noHun_false() = + kosmos.runTest { + val latest by collectLastValue(underTest.canShowOngoingActivityChips) + + fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + testScope = testScope, + ) + kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) + + assertThat(latest).isFalse() + } + + @Test + fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hun_false() = + kosmos.runTest { + val latest by collectLastValue(underTest.canShowOngoingActivityChips) + + transitionKeyguardToGone() + + headsUpNotificationRepository.setNotifications( + UnconfinedFakeHeadsUpRowRepository( + key = "key", + pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser), + ) + ) + + assertThat(latest).isFalse() + } + + @Test fun isClockVisible_allowedByDisableFlags_visible() = kosmos.runTest { val latest by collectLastValue(underTest.isClockVisible) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index d3218ad8c9fb..a0bf7f00b11c 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -203,6 +203,8 @@ public interface QS extends FragmentBase { */ void setIsNotificationPanelFullWidth(boolean isFullWidth); + default void setQSExpandingOrCollapsing(boolean isQSExpandingOrCollapsing) {} + /** * Callback for when QSPanel container is scrolled */ diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java index 94fdbae83253..9b961d2535ae 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java @@ -122,7 +122,7 @@ public interface NotificationMenuRowPlugin extends Plugin { public void setAppName(String appName); - public void createMenu(ViewGroup parent, StatusBarNotification sbn); + public void createMenu(ViewGroup parent); public void resetMenu(); @@ -215,9 +215,8 @@ public interface NotificationMenuRowPlugin extends Plugin { /** * Callback used to signal the menu that its parent notification has been updated. - * @param sbn */ - public void onNotificationUpdated(StatusBarNotification sbn); + public void onNotificationUpdated(); /** * Callback used to signal the menu that a user is moving the parent notification. diff --git a/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml b/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml index fa9318bc151c..6b55ac2e0398 100644 --- a/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml +++ b/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml @@ -27,7 +27,6 @@ android:layout_height="@dimen/status_bar_bindable_icon_size" android:layout_width="wrap_content" android:layout_gravity="center_vertical" - android:padding="4sp" /> </com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarComposeIconView> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 17a89b3a0394..640e1fa79530 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -491,6 +491,9 @@ <!-- some constraints use a negative margin. must be aligned with overlay_border_width, above; overlay_border_width_neg = overlay_border_width * -1 --> <dimen name="overlay_border_width_neg">-4dp</dimen> + <dimen name="overlay_shade_panel_shape_radius"> + @dimen/aux_spacing_overlay_panel_shape_radius + </dimen> <dimen name="clipboard_preview_size">@dimen/overlay_x_scale</dimen> <dimen name="clipboard_overlay_min_font">10sp</dimen> @@ -2215,4 +2218,8 @@ <dimen name="rear_display_progress_width">231dp</dimen> <!-- Rear display mode end --> + <!-- Spacing attributes to overwrite --> + <dimen name="aux_spacing_overlay_panel_shape_radius">46dp</dimen> + <!-- Spacing attributes to overwrite end --> + </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3fc46ed6c9d1..359bd2bcb37c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3170,8 +3170,8 @@ <string name="controls_media_settings_button">Settings</string> <!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]--> <string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string> - <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] --> - <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string> + <!-- Content description for media controls progress bar [CHAR_LIMIT=NONE] --> + <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1 hour 2 minutes 30 seconds">%1$s</xliff:g> of <xliff:g id="total_time" example="4 hours 5 seconds">%2$s</xliff:g></string> <!-- Placeholder title to inform user that an app has posted media controls [CHAR_LIMIT=NONE] --> <string name="controls_media_empty_title"><xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g> is running</string> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 0f1da509468a..ae3a76e2d2ca 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -71,6 +71,7 @@ android_library { "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "//frameworks/libs/systemui:msdl", "//frameworks/libs/systemui:view_capture", + "am_flags_lib", ], resource_dirs: [ "res", diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml index f22dac4b9fb4..98e5cea0e78f 100644 --- a/packages/SystemUI/shared/res/values/bools.xml +++ b/packages/SystemUI/shared/res/values/bools.xml @@ -22,4 +22,7 @@ <resources> <!-- Whether to add padding at the bottom of the complication clock --> <bool name="dream_overlay_complication_clock_bottom_padding">false</bool> -</resources>
\ No newline at end of file + + <!-- Whether to mark tasks that are present in the UI as perceptible tasks. --> + <bool name="config_usePerceptibleTasks">false</bool> +</resources> 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 9ebb15f43307..c82243934b8b 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 @@ -19,6 +19,7 @@ package com.android.systemui.shared.recents.utilities; import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME; import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE; import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import android.annotation.TargetApi; import android.app.StatusBarManager.NavbarFlags; @@ -35,6 +36,8 @@ import android.util.DisplayMetrics; import android.view.Surface; import android.view.WindowManager; +import com.android.systemui.shared.recents.model.Task; + /* Common code */ public class Utilities { @@ -165,4 +168,10 @@ public class Utilities { float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT; return (size / densityRatio); } + + /** Whether a task is in freeform mode. */ + public static boolean isFreeformTask(Task task) { + return task != null && task.getKey() != null + && task.getKey().windowingMode == WINDOWING_MODE_FREEFORM; + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index ed9ba7aa455b..487d1ce2514e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -46,6 +46,8 @@ import android.view.Display; import android.window.TaskSnapshot; import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.server.am.Flags; +import com.android.systemui.shared.R; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -227,6 +229,17 @@ public class ActivityManagerWrapper { } /** + * Sets whether or not the specified task is perceptible. + */ + public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) { + try { + return getService().setTaskIsPerceptible(taskId, isPerceptible); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Removes a task by id. */ public void removeTask(final int taskId) { @@ -311,10 +324,23 @@ public class ActivityManagerWrapper { } /** + * Returns true if tasks with a presence in the UI should be marked as perceptible tasks. + */ + public static boolean usePerceptibleTasks(Context context) { + return Flags.perceptibleTasks() + && context.getResources().getBoolean(R.bool.config_usePerceptibleTasks); + } + + /** * Returns true if the running task represents the home task */ public static boolean isHomeTask(RunningTaskInfo info) { return info.configuration.windowConfiguration.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME; } + + public boolean isRunningInTestHarness() { + return ActivityManager.isRunningInTestHarness() + || ActivityManager.isRunningInUserTestHarness(); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java index 5e36539ecbec..a7bb11ea0442 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java +++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java @@ -156,7 +156,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { mMainExecutor.execute(() -> mView.updateEmergencyCallButton( /* isInCall= */ isInCall, /* hasTelephonyRadio= */ getContext().getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_TELEPHONY), + .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING), /* simLocked= */ mKeyguardUpdateMonitor.isSimPinVoiceSecure(), /* isSecure= */ isSecure)); }); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java index 7c141c1b561e..5247acc94e03 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java @@ -58,11 +58,22 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi private final BiMap<Integer, AmbientVolumeSlider> mSideToSliderMap = HashBiMap.create(); private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT; + private HearingDevicesUiEventLogger mUiEventLogger; + private int mLaunchSourceId; + private final AmbientVolumeSlider.OnChangeListener mSliderOnChangeListener = (slider, value) -> { - if (mListener != null) { - final int side = mSideToSliderMap.inverse().get(slider); - mListener.onSliderValueChange(side, value); + final Integer side = mSideToSliderMap.inverse().get(slider); + if (side != null) { + if (mUiEventLogger != null) { + HearingDevicesUiEvent uiEvent = side == SIDE_UNIFIED + ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED + : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED; + mUiEventLogger.log(uiEvent, mLaunchSourceId); + } + if (mListener != null) { + mListener.onSliderValueChange(side, value); + } } }; @@ -94,6 +105,12 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi return; } setMuted(!mMuted); + if (mUiEventLogger != null) { + HearingDevicesUiEvent uiEvent = mMuted + ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_MUTE + : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_UNMUTE; + mUiEventLogger.log(uiEvent, mLaunchSourceId); + } if (mListener != null) { mListener.onAmbientVolumeIconClick(); } @@ -103,6 +120,12 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi mExpandIcon = requireViewById(R.id.ambient_expand_icon); mExpandIcon.setOnClickListener(v -> { setExpanded(!mExpanded); + if (mUiEventLogger != null) { + HearingDevicesUiEvent uiEvent = mExpanded + ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS + : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS; + mUiEventLogger.log(uiEvent, mLaunchSourceId); + } if (mListener != null) { mListener.onExpandIconClick(); } @@ -243,6 +266,11 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi updateVolumeLevel(); } + void setUiEventLogger(HearingDevicesUiEventLogger uiEventLogger, int launchSourceId) { + mUiEventLogger = uiEventLogger; + mLaunchSourceId = launchSourceId; + } + private void updateVolumeLevel() { int leftLevel, rightLevel; if (mExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 22ecb0af8c18..786d27af3994 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -382,6 +382,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private void setupAmbientControls(CachedBluetoothDevice activeHearingDevice) { final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout); + ambientLayout.setUiEventLogger(mUiEventLogger, mLaunchSourceId); mAmbientController = new AmbientVolumeUiController( mDialog.getContext(), mLocalBluetoothManager, ambientLayout); mAmbientController.setShowUiWhenLocalDataExist(false); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt index 9e77b02be495..fe1d5040c6f5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt @@ -29,7 +29,17 @@ enum class HearingDevicesUiEvent(private val id: Int) : UiEventLogger.UiEventEnu @UiEvent(doc = "Click on the device gear to enter device detail page") HEARING_DEVICES_GEAR_CLICK(1853), @UiEvent(doc = "Select a preset from preset spinner") HEARING_DEVICES_PRESET_SELECT(1854), - @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856); + @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856), + @UiEvent(doc = "Change the ambient volume with unified control") + HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED(2149), + @UiEvent(doc = "Change the ambient volume with separated control") + HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED(2150), + @UiEvent(doc = "Mute the ambient volume") HEARING_DEVICES_AMBIENT_MUTE(2151), + @UiEvent(doc = "Unmute the ambient volume") HEARING_DEVICES_AMBIENT_UNMUTE(2152), + @UiEvent(doc = "Expand the ambient volume controls") + HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS(2153), + @UiEvent(doc = "Collapse the ambient volume controls") + HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154); override fun getId(): Int = this.id } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index cce1ae1a2947..6473b1c30586 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -237,7 +237,15 @@ constructor( with(mediaHost) { expansion = MediaHostState.EXPANDED expandedMatchesParentHeight = true - showsOnlyActiveMedia = false + if (v2FlagEnabled()) { + // Only show active media to match lock screen, not resumable media, which can + // persist + // for up to 2 days. + showsOnlyActiveMedia = true + } else { + // Maintain old behavior on tablet until V2 flag rolls out. + showsOnlyActiveMedia = false + } falsingProtectionNeeded = false disablePagination = true init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 97ad2d7d36ff..6b1248b6983e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -247,14 +247,13 @@ public interface KeyguardModule { @Provides @SysUISingleton static BlurConfig provideBlurConfig(@Main Resources resources) { - int minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius); int maxBlurRadius = Flags.notificationShadeBlur() || Flags.bouncerUiRevamp() || Flags.glanceableHubBlurredBackground() ? resources.getDimensionPixelSize(R.dimen.max_shade_window_blur_radius) : resources.getDimensionPixelSize(R.dimen.max_window_blur_radius); - return new BlurConfig(minBlurRadius, maxBlurRadius); + return new BlurConfig(0.0f, maxBlurRadius); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt index 6d796d96ea71..3f538203aee9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt @@ -49,9 +49,10 @@ import com.android.systemui.media.controls.ui.util.MediaArtworkHelper import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.viewmodel.MediaActionViewModel import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel -import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_CENTER_ALPHA import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA +import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA +import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_ALL import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_COMPACT import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel @@ -537,18 +538,24 @@ object MediaControlViewBinder { height: Int, ): LayerDrawable { val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height) - val alpha = + val startAlpha = if (Flags.mediaControlsA11yColors()) { - MEDIA_PLAYER_SCRIM_CENTER_ALPHA - } else { MEDIA_PLAYER_SCRIM_START_ALPHA + } else { + MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY + } + val endAlpha = + if (Flags.mediaControlsA11yColors()) { + MEDIA_PLAYER_SCRIM_END_ALPHA + } else { + MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY } return MediaArtworkHelper.setUpGradientColorOnDrawable( albumArt, context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable, mutableColorScheme, - alpha, - MEDIA_PLAYER_SCRIM_END_ALPHA, + startAlpha, + endAlpha, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt index 34f7c4dcaec0..c9716be52408 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt @@ -18,6 +18,9 @@ package com.android.systemui.media.controls.ui.binder import android.animation.Animator import android.animation.ObjectAnimator +import android.icu.text.MeasureFormat +import android.icu.util.Measure +import android.icu.util.MeasureUnit import android.text.format.DateUtils import androidx.annotation.UiThread import androidx.lifecycle.Observer @@ -28,8 +31,11 @@ import com.android.systemui.media.controls.ui.drawable.SquigglyProgress import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel import com.android.systemui.res.R +import java.util.Locale private const val TAG = "SeekBarObserver" +private const val MIN_IN_SEC = 60 +private const val HOUR_IN_SEC = MIN_IN_SEC * 60 /** * Observer for changes from SeekBarViewModel. @@ -127,10 +133,9 @@ open class SeekBarObserver(private val holder: MediaViewHolder) : } holder.seekBar.setMax(data.duration) - val totalTimeString = - DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS) + val totalTimeDescription = formatTimeContentDescription(data.duration) if (data.scrubbing) { - holder.scrubbingTotalTimeView.text = totalTimeString + holder.scrubbingTotalTimeView.text = formatTimeLabel(data.duration) } data.elapsedTime?.let { @@ -148,20 +153,62 @@ open class SeekBarObserver(private val holder: MediaViewHolder) : } } - val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS) + val elapsedTimeDescription = formatTimeContentDescription(it) if (data.scrubbing) { - holder.scrubbingElapsedTimeView.text = elapsedTimeString + holder.scrubbingElapsedTimeView.text = formatTimeLabel(it) } holder.seekBar.contentDescription = holder.seekBar.context.getString( R.string.controls_media_seekbar_description, - elapsedTimeString, - totalTimeString + elapsedTimeDescription, + totalTimeDescription, ) } } + /** Returns a time string suitable for display, e.g. "12:34" */ + private fun formatTimeLabel(milliseconds: Int): CharSequence { + return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS) + } + + /** + * Returns a time string suitable for content description, e.g. "12 minutes 34 seconds" + * + * Follows same logic as Chronometer#formatDuration + */ + private fun formatTimeContentDescription(milliseconds: Int): CharSequence { + var seconds = milliseconds / DateUtils.SECOND_IN_MILLIS + + val hours = + if (seconds >= HOUR_IN_SEC) { + seconds / HOUR_IN_SEC + } else { + 0 + } + seconds -= hours * HOUR_IN_SEC + + val minutes = + if (seconds >= MIN_IN_SEC) { + seconds / MIN_IN_SEC + } else { + 0 + } + seconds -= minutes * MIN_IN_SEC + + val measures = arrayListOf<Measure>() + if (hours > 0) { + measures.add(Measure(hours, MeasureUnit.HOUR)) + } + if (minutes > 0) { + measures.add(Measure(minutes, MeasureUnit.MINUTE)) + } + measures.add(Measure(seconds, MeasureUnit.SECOND)) + + return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) + .formatMeasures(*measures.toTypedArray()) + } + @VisibleForTesting open fun buildResetAnimator(targetTime: Int): Animator { val animator = @@ -169,7 +216,7 @@ open class SeekBarObserver(private val holder: MediaViewHolder) : holder.seekBar, "progress", holder.seekBar.progress, - targetTime + RESET_ANIMATION_DURATION_MS + targetTime + RESET_ANIMATION_DURATION_MS, ) animator.setAutoCancel(true) animator.duration = RESET_ANIMATION_DURATION_MS.toLong() diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 39c08daf53d6..694a4c7e493d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -23,7 +23,10 @@ import static com.android.systemui.Flags.communalHub; import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation; import static com.android.systemui.media.controls.domain.pipeline.MediaActionsKt.getNotificationActions; import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS; -import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_CENTER_ALPHA; +import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA; +import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY; +import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA; +import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY; import android.animation.Animator; import android.animation.AnimatorInflater; @@ -176,9 +179,7 @@ public class MediaControlPanel { protected static final int SMARTSPACE_CARD_DISMISS_EVENT = 761; private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f; - private static final float MEDIA_SCRIM_START_ALPHA = 0.25f; private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f; - private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f; private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f; private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS); @@ -1093,11 +1094,12 @@ public class MediaControlPanel { Drawable albumArt = getScaledBackground(artworkIcon, width, height); GradientDrawable gradient = (GradientDrawable) mContext.getDrawable( R.drawable.qs_media_scrim).mutate(); - float startAlpha = (Flags.mediaControlsA11yColors()) - ? MEDIA_PLAYER_SCRIM_CENTER_ALPHA - : MEDIA_SCRIM_START_ALPHA; + if (Flags.mediaControlsA11yColors()) { + return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme, + MEDIA_PLAYER_SCRIM_START_ALPHA, MEDIA_PLAYER_SCRIM_END_ALPHA); + } return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme, - startAlpha, MEDIA_PLAYER_SCRIM_END_ALPHA); + MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY, MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index 198155b3f297..b687dce20b06 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -1137,6 +1137,7 @@ constructor( ) { gutsViewHolder.gutsText.setTypeface(menuTF) gutsViewHolder.dismissText.setTypeface(menuTF) + gutsViewHolder.cancelText.setTypeface(menuTF) titleText.setTypeface(titleTF) artistText.setTypeface(artistTF) seamlessText.setTypeface(menuTF) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index 9153e17393d2..bcda485272c2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt @@ -419,8 +419,10 @@ class MediaControlViewModel( const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L @Deprecated("Remove with media_controls_a11y_colors flag") - const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.25f - const val MEDIA_PLAYER_SCRIM_CENTER_ALPHA = 0.75f - const val MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f + const val MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY = 0.25f + @Deprecated("Remove with media_controls_a11y_colors flag") + const val MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY = 1.0f + const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.65f + const val MEDIA_PLAYER_SCRIM_END_ALPHA = 0.75f } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt index 24bb16a11fe7..3a81102699f7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt @@ -27,9 +27,7 @@ object QSEvents { private set fun setLoggerForTesting(): UiEventLoggerFake { - return UiEventLoggerFake().also { - qsUiEventsLogger = it - } + return UiEventLoggerFake().also { qsUiEventsLogger = it } } fun resetLogger() { @@ -40,32 +38,28 @@ object QSEvents { enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum { @UiEvent(doc = "Tile clicked. It has an instance id and a spec (or packageName)") QS_ACTION_CLICK(387), - - @UiEvent(doc = "Tile secondary button clicked. " + - "It has an instance id and a spec (or packageName)") + @UiEvent( + doc = + "Tile secondary button clicked. " + "It has an instance id and a spec (or packageName)" + ) QS_ACTION_SECONDARY_CLICK(388), - @UiEvent(doc = "Tile long clicked. It has an instance id and a spec (or packageName)") QS_ACTION_LONG_PRESS(389), - - @UiEvent(doc = "Quick Settings panel expanded") - QS_PANEL_EXPANDED(390), - - @UiEvent(doc = "Quick Settings panel collapsed") - QS_PANEL_COLLAPSED(391), - - @UiEvent(doc = "Tile visible in Quick Settings panel. The tile may be in a different page. " + - "It has an instance id and a spec (or packageName)") + @UiEvent(doc = "Quick Settings panel expanded") QS_PANEL_EXPANDED(390), + @UiEvent(doc = "Quick Settings panel collapsed") QS_PANEL_COLLAPSED(391), + @UiEvent( + doc = + "Tile visible in Quick Settings panel. The tile may be in a different page. " + + "It has an instance id and a spec (or packageName)" + ) QS_TILE_VISIBLE(392), - - @UiEvent(doc = "Quick Quick Settings panel expanded") - QQS_PANEL_EXPANDED(393), - - @UiEvent(doc = "Quick Quick Settings panel collapsed") - QQS_PANEL_COLLAPSED(394), - - @UiEvent(doc = "Tile visible in Quick Quick Settings panel. " + - "It has an instance id and a spec (or packageName)") + @UiEvent(doc = "Quick Quick Settings panel expanded") QQS_PANEL_EXPANDED(393), + @UiEvent(doc = "Quick Quick Settings panel collapsed") QQS_PANEL_COLLAPSED(394), + @UiEvent( + doc = + "Tile visible in Quick Quick Settings panel. " + + "It has an instance id and a spec (or packageName)" + ) QQS_TILE_VISIBLE(395); override fun getId() = _id @@ -73,47 +67,32 @@ enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum { enum class QSEditEvent(private val _id: Int) : UiEventLogger.UiEventEnum { - @UiEvent(doc = "Tile removed from current tiles") - QS_EDIT_REMOVE(210), - - @UiEvent(doc = "Tile added to current tiles") - QS_EDIT_ADD(211), - - @UiEvent(doc = "Tile moved") - QS_EDIT_MOVE(212), - - @UiEvent(doc = "QS customizer open") - QS_EDIT_OPEN(213), - - @UiEvent(doc = "QS customizer closed") - QS_EDIT_CLOSED(214), - - @UiEvent(doc = "QS tiles reset") - QS_EDIT_RESET(215); + @UiEvent(doc = "Tile removed from current tiles") QS_EDIT_REMOVE(210), + @UiEvent(doc = "Tile added to current tiles") QS_EDIT_ADD(211), + @UiEvent(doc = "Tile moved") QS_EDIT_MOVE(212), + @UiEvent(doc = "QS customizer open") QS_EDIT_OPEN(213), + @UiEvent(doc = "QS customizer closed") QS_EDIT_CLOSED(214), + @UiEvent(doc = "QS tiles reset") QS_EDIT_RESET(215), + @UiEvent(doc = "QS edit mode resize tile to large") QS_EDIT_RESIZE_LARGE(2122), + @UiEvent(doc = "QS edit mode resize tile to small") QS_EDIT_RESIZE_SMALL(2123); override fun getId() = _id } /** - * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger} - * Other names for DND (Do Not Disturb) include "Zen" and "Priority mode". + * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger} Other names for DND (Do + * Not Disturb) include "Zen" and "Priority mode". */ enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum { - @UiEvent(doc = "User selected an option on the DND dialog") - QS_DND_CONDITION_SELECT(420), - + @UiEvent(doc = "User selected an option on the DND dialog") QS_DND_CONDITION_SELECT(420), @UiEvent(doc = "User increased countdown duration of DND from the DND dialog") QS_DND_TIME_UP(422), - @UiEvent(doc = "User decreased countdown duration of DND from the DND dialog") QS_DND_TIME_DOWN(423), - @UiEvent(doc = "User enabled DND from the QS DND dialog to last until manually turned off") QS_DND_DIALOG_ENABLE_FOREVER(946), - @UiEvent(doc = "User enabled DND from the QS DND dialog to last until the next alarm goes off") QS_DND_DIALOG_ENABLE_UNTIL_ALARM(947), - @UiEvent(doc = "User enabled DND from the QS DND dialog to last until countdown is done") QS_DND_DIALOG_ENABLE_UNTIL_COUNTDOWN(948); @@ -121,29 +100,17 @@ enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum { } enum class QSUserSwitcherEvent(private val _id: Int) : UiEventLogger.UiEventEnum { - @UiEvent(doc = "The current user has been switched in the detail panel") - QS_USER_SWITCH(424), - - @UiEvent(doc = "User switcher QS dialog open") - QS_USER_DETAIL_OPEN(425), - - @UiEvent(doc = "User switcher QS dialog closed") - QS_USER_DETAIL_CLOSE(426), - - @UiEvent(doc = "User switcher QS dialog more settings pressed") - QS_USER_MORE_SETTINGS(427), - - @UiEvent(doc = "The user has added a guest in the detail panel") - QS_USER_GUEST_ADD(754), - + @UiEvent(doc = "The current user has been switched in the detail panel") QS_USER_SWITCH(424), + @UiEvent(doc = "User switcher QS dialog open") QS_USER_DETAIL_OPEN(425), + @UiEvent(doc = "User switcher QS dialog closed") QS_USER_DETAIL_CLOSE(426), + @UiEvent(doc = "User switcher QS dialog more settings pressed") QS_USER_MORE_SETTINGS(427), + @UiEvent(doc = "The user has added a guest in the detail panel") QS_USER_GUEST_ADD(754), @UiEvent(doc = "The user selected 'Start over' after switching to the existing Guest user") QS_USER_GUEST_WIPE(755), - @UiEvent(doc = "The user selected 'Yes, continue' after switching to the existing Guest user") QS_USER_GUEST_CONTINUE(756), - @UiEvent(doc = "The user has pressed 'Remove guest' in the detail panel") QS_USER_GUEST_REMOVE(757); override fun getId() = _id -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 6ad8bae05d7a..5930a24e01a0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -306,6 +306,7 @@ constructor( sceneState, viewModel.containerViewModel.editModeViewModel.isEditing, snapshotFlow { viewModel.expansionState }.map { it.progress }, + snapshotFlow { viewModel.isQSExpandingOrCollapsing }, ) } @@ -537,6 +538,10 @@ constructor( return qqsVisible.value } + override fun setQSExpandingOrCollapsing(isQSExpandingOrCollapsing: Boolean) { + viewModel.isQSExpandingOrCollapsing = isQSExpandingOrCollapsing + } + private fun setListenerCollections() { lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -877,6 +882,7 @@ private suspend fun synchronizeQsState( state: MutableSceneTransitionLayoutState, editMode: Flow<Boolean>, expansion: Flow<Float>, + isQSExpandingOrCollapsing: Flow<Boolean>, ) { coroutineScope { val animationScope = this @@ -888,31 +894,46 @@ private suspend fun synchronizeQsState( currentTransition = null } - editMode.combine(expansion, ::Pair).collectLatest { (editMode, progress) -> + var lastValidProgress = 0f + combine(editMode, expansion, isQSExpandingOrCollapsing, ::Triple).collectLatest { + (editMode, progress, isQSExpandingOrCollapsing) -> if (editMode && state.currentScene != SceneKeys.EditMode) { state.setTargetScene(SceneKeys.EditMode, animationScope)?.second?.join() } else if (!editMode && state.currentScene == SceneKeys.EditMode) { state.setTargetScene(SceneKeys.QuickSettings, animationScope)?.second?.join() } + if (!editMode) { - when (progress) { - 0f -> snapTo(QuickQuickSettings) - 1f -> snapTo(QuickSettings) - else -> { - val transition = currentTransition - if (transition != null) { - transition.progress = progress - return@collectLatest - } + if (!isQSExpandingOrCollapsing) { + if (progress == 0f) { + snapTo(QuickQuickSettings) + return@collectLatest + } - val newTransition = - ExpansionTransition(progress).also { currentTransition = it } - state.startTransitionImmediately( - animationScope = animationScope, - transition = newTransition, - ) + if (progress == 1f) { + snapTo(QuickSettings) + return@collectLatest } } + + var progress = progress + if (progress >= 0f || progress <= 1f) { + lastValidProgress = progress + } else { + progress = lastValidProgress + } + + val transition = currentTransition + if (transition != null) { + transition.progress = progress + return@collectLatest + } + + val newTransition = ExpansionTransition(progress).also { currentTransition = it } + state.startTransitionImmediately( + animationScope = animationScope, + transition = newTransition, + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt index ff84479ebcad..b829bbce2f18 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -306,6 +306,8 @@ constructor( val animateTilesExpansion: Boolean get() = inFirstPage && !mediaSuddenlyAppearingInLandscape + var isQSExpandingOrCollapsing by mutableStateOf(false) + private val inFirstPage: Boolean get() = inFirstPageViewModel.inFirstPage @@ -539,6 +541,7 @@ constructor( println("proposedTranslation", proposedTranslation) println("expansionState", expansionState) println("forceQS", forceQs) + println("isShadeExpandingOrCollapsing", isQSExpandingOrCollapsing) printSection("Derived values") { println("headerTranslation", headerTranslation) println("translationScaleY", translationScaleY) diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt index 482cd4014acf..3f279b0f7a74 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt @@ -16,15 +16,18 @@ package com.android.systemui.qs.panels.domain.interactor +import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel +import com.android.systemui.qs.QSEditEvent import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository import com.android.systemui.qs.panels.data.repository.LargeTileSpanRepository import com.android.systemui.qs.panels.shared.model.PanelsLog import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.metricSpec import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -40,6 +43,7 @@ constructor( private val repo: DefaultLargeTilesRepository, private val currentTilesInteractor: CurrentTilesInteractor, private val preferencesInteractor: QSPreferencesInteractor, + private val uiEventLogger: UiEventLogger, largeTilesSpanRepo: LargeTileSpanRepository, @PanelsLog private val logBuffer: LogBuffer, @Application private val applicationScope: CoroutineScope, @@ -70,8 +74,18 @@ constructor( val isIcon = !largeTilesSpecs.value.contains(spec) if (toIcon && !isIcon) { preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec) + uiEventLogger.log( + /* event= */ QSEditEvent.QS_EDIT_RESIZE_SMALL, + /* uid= */ 0, + /* packageName= */ spec.metricSpec, + ) } else if (!toIcon && isIcon) { preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec) + uiEventLogger.log( + /* event= */ QSEditEvent.QS_EDIT_RESIZE_LARGE, + /* uid= */ 0, + /* packageName= */ spec.metricSpec, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index d05837261b89..34b3324f81da 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -105,15 +105,14 @@ import com.android.systemui.util.kotlin.JavaAdapter; import dalvik.annotation.optimization.NeverCompile; -import dagger.Lazy; - -import kotlin.Unit; - import java.io.PrintWriter; import javax.inject.Inject; import javax.inject.Provider; +import dagger.Lazy; +import kotlin.Unit; + /** Handles QuickSettings touch handling, expansion and animation state. */ @SysUISingleton public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable { @@ -2366,8 +2365,16 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum return; } if (startTracing) { + if (mQs != null) { + mQs.setQSExpandingOrCollapsing(true); + } + monitor.begin(mPanelView, Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); } else { + if (mQs != null) { + mQs.setQSExpandingOrCollapsing(false); + } + if (wasCancelled) { monitor.cancel(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); } else { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt index 4adc1a5ae746..20b44d73e097 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt @@ -22,7 +22,9 @@ import android.icu.text.DateFormat import android.icu.text.DisplayContext import android.provider.Settings import android.view.ViewGroup +import androidx.compose.material3.ColorScheme import androidx.compose.runtime.getValue +import androidx.compose.ui.graphics.Color import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.lifecycle.ExclusiveActivatable @@ -244,11 +246,29 @@ constructor( /** Represents the background highlight of a header icons chip. */ sealed interface HeaderChipHighlight { - data object None : HeaderChipHighlight - data object Weak : HeaderChipHighlight + fun backgroundColor(colorScheme: ColorScheme): Color - data object Strong : HeaderChipHighlight + fun foregroundColor(colorScheme: ColorScheme): Color + + data object None : HeaderChipHighlight { + override fun backgroundColor(colorScheme: ColorScheme): Color = Color.Unspecified + + override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary + } + + data object Weak : HeaderChipHighlight { + override fun backgroundColor(colorScheme: ColorScheme): Color = + colorScheme.primary.copy(alpha = 0.1f) + + override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary + } + + data object Strong : HeaderChipHighlight { + override fun backgroundColor(colorScheme: ColorScheme): Color = colorScheme.secondary + + override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.onSecondary + } } private fun getFormatFromPattern(pattern: String?): DateFormat { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt index f30043eece62..f45971b57b65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt @@ -45,7 +45,7 @@ open class BlurUtils @Inject constructor( private val crossWindowBlurListeners: CrossWindowBlurListeners, dumpManager: DumpManager ) : Dumpable { - val minBlurRadius = blurConfig.minBlurRadiusPx + val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius).toFloat(); val maxBlurRadius = if (Flags.notificationShadeBlur()) { blurConfig.maxBlurRadiusPx } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java index c1b8d9d123b9..6ebe02469f5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static android.app.Flags.notificationsRedesignTemplates; + import android.app.Flags; import android.app.Notification; import android.graphics.drawable.Drawable; @@ -427,7 +429,8 @@ public class NotificationGroupingUtil { @Override public void apply(View parent, View view, boolean apply, boolean reset) { - if (reset && parent instanceof ConversationLayout) { + if (!notificationsRedesignTemplates() + && reset && parent instanceof ConversationLayout) { ConversationLayout layout = (ConversationLayout) parent; apply = layout.shouldHideAppName(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index f06565f1b6d2..32da6fff6bcc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -24,7 +24,7 @@ import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_ import static android.os.Flags.allowPrivateProfile; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_NULL; -import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY; +import static android.provider.Settings.Secure.OTP_NOTIFICATION_REDACTION_LOCK_TIME; import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI; @@ -124,10 +124,10 @@ public class NotificationLockscreenUserManagerImpl implements private static final Uri REDACT_OTP_ON_WIFI = Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI); - private static final Uri REDACT_OTP_IMMEDIATELY = - Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_IMMEDIATELY); + private static final Uri OTP_REDACTION_LOCK_TIME = + Settings.Secure.getUriFor(OTP_NOTIFICATION_REDACTION_LOCK_TIME); - private static final long LOCK_TIME_FOR_SENSITIVE_REDACTION_MS = + private static final long DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS = TimeUnit.MINUTES.toMillis(10); private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy; private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy; @@ -316,7 +316,8 @@ public class NotificationLockscreenUserManagerImpl implements protected final AtomicBoolean mConnectedToWifi = new AtomicBoolean(false); protected final AtomicBoolean mRedactOtpOnWifi = new AtomicBoolean(true); - protected final AtomicBoolean mRedactOtpImmediately = new AtomicBoolean(false); + protected final AtomicLong mOtpRedactionRequiredLockTimeMs = + new AtomicLong(DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS); protected int mCurrentUserId = 0; @@ -375,7 +376,7 @@ public class NotificationLockscreenUserManagerImpl implements mLockScreenUris.add(SHOW_LOCKSCREEN); mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN); mLockScreenUris.add(REDACT_OTP_ON_WIFI); - mLockScreenUris.add(REDACT_OTP_IMMEDIATELY); + mLockScreenUris.add(OTP_REDACTION_LOCK_TIME); dumpManager.registerDumpable(this); @@ -447,8 +448,8 @@ public class NotificationLockscreenUserManagerImpl implements changed |= updateUserShowPrivateSettings(user.getIdentifier()); } else if (REDACT_OTP_ON_WIFI.equals(uri)) { changed |= updateRedactOtpOnWifiSetting(); - } else if (REDACT_OTP_IMMEDIATELY.equals(uri)) { - changed |= updateRedactOtpImmediatelySetting(); + } else if (OTP_REDACTION_LOCK_TIME.equals(uri)) { + changed |= updateOtpLockTimeSetting(); } } @@ -487,7 +488,7 @@ public class NotificationLockscreenUserManagerImpl implements mLockscreenSettingsObserver ); mSecureSettings.registerContentObserverAsync( - REDACT_OTP_IMMEDIATELY, + OTP_REDACTION_LOCK_TIME, mLockscreenSettingsObserver ); @@ -638,13 +639,13 @@ public class NotificationLockscreenUserManagerImpl implements } @WorkerThread - private boolean updateRedactOtpImmediatelySetting() { - boolean originalValue = mRedactOtpImmediately.get(); - boolean newValue = mSecureSettings.getIntForUser( - REDACT_OTP_NOTIFICATION_IMMEDIATELY, - 0, - Process.myUserHandle().getIdentifier()) != 0; - mRedactOtpImmediately.set(newValue); + private boolean updateOtpLockTimeSetting() { + long originalValue = mOtpRedactionRequiredLockTimeMs.get(); + long newValue = mSecureSettings.getLongForUser( + OTP_NOTIFICATION_REDACTION_LOCK_TIME, + DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS, + Process.myUserHandle().getIdentifier()); + mOtpRedactionRequiredLockTimeMs.set(newValue); return originalValue != newValue; } @@ -832,14 +833,9 @@ public class NotificationLockscreenUserManagerImpl implements return false; } - long latestTimeForRedaction; - if (mRedactOtpImmediately.get()) { - latestTimeForRedaction = mLastLockTime.get(); - } else { - // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS - // when this notification arrived, do not redact - latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS; - } + // If the lock screen was not already locked for at least mOtpRedactionRequiredLockTimeMs + // when this notification arrived, do not redact + long latestTimeForRedaction = mLastLockTime.get() + mOtpRedactionRequiredLockTimeMs.get(); if (ent.getSbn().getPostTime() < latestTimeForRedaction) { return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 6aa2fe29e768..3db004848d22 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -110,6 +110,10 @@ constructor( private var prevTimestamp: Long = -1 private var prevShadeDirection = 0 private var prevShadeVelocity = 0f + // tracks whether app launch transition is in progress. This involves two independent factors + // that control blur, shade expansion and app launch animation from outside sysui. + // They can complete out of order, this flag will be reset by the animation that finishes later. + private var appLaunchTransitionIsInProgress = false // Only for dumpsys private var lastAppliedBlur = 0 @@ -158,6 +162,18 @@ constructor( if (field == value) { return } + // Set this to true now, this will be reset when the next shade expansion finishes or + // when the app launch finishes, whichever happens later. + if (value) { + appLaunchTransitionIsInProgress = true + } else { + // App was launching and now it has finished launching + if (shadeExpansion == 0.0f) { + // this means shade expansion finished before app launch was done. + // reset the flag here + appLaunchTransitionIsInProgress = false + } + } field = value scheduleUpdate() @@ -172,6 +188,12 @@ constructor( shadeAnimation.animateTo(0) shadeAnimation.finishIfRunning() } + @Deprecated( + message = + "This might get reset to false before shade expansion is fully done, " + + "consider using areBlursDisabledForAppLaunch" + ) + get() = field private var zoomOutCalculatedFromShadeRadius: Float = 0.0f @@ -183,6 +205,11 @@ constructor( scheduleUpdate() } + private val areBlursDisabledForAppLaunch: Boolean + get() = + blursDisabledForAppLaunch || + (Flags.bouncerUiRevamp() && appLaunchTransitionIsInProgress) + /** Force stop blur effect when necessary. */ private var scrimsVisible: Boolean = false set(value) { @@ -221,7 +248,7 @@ constructor( combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress)) var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius) - if (blursDisabledForAppLaunch || blursDisabledForUnlock) { + if (areBlursDisabledForAppLaunch || blursDisabledForUnlock) { shadeRadius = 0f } @@ -259,7 +286,7 @@ constructor( private val shouldBlurBeOpaque: Boolean get() = if (Flags.notificationShadeBlur()) false - else scrimsVisible && !blursDisabledForAppLaunch + else scrimsVisible && !areBlursDisabledForAppLaunch /** Callback that updates the window blur value and is called only once per frame. */ @VisibleForTesting @@ -442,6 +469,13 @@ constructor( val shadeDirection = sign(diff).toInt() val shadeVelocity = MathUtils.constrain(VELOCITY_SCALE * diff / deltaTime, MIN_VELOCITY, MAX_VELOCITY) + if (expansion == 0.0f && appLaunchTransitionIsInProgress && !blursDisabledForAppLaunch) { + // Shade expansion finished but the app launch is already done, then this should mark + // the transition as done. + Log.d(TAG, "appLaunchTransitionIsInProgress is now false from shade expansion event") + appLaunchTransitionIsInProgress = false + } + updateShadeAnimationBlur(expansion, tracking, shadeVelocity, shadeDirection) prevShadeDirection = shadeDirection @@ -553,6 +587,7 @@ constructor( it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}") it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius") it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch") + it.println("appLaunchTransitionIsInProgress: $appLaunchTransitionIsInProgress") it.println("qsPanelExpansion: $qsPanelExpansion") it.println("transitionToFullShadeProgress: $transitionToFullShadeProgress") it.println("lastAppliedBlur: $lastAppliedBlur") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt index 2eae3eb4fc19..7548f6ff5bd2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt @@ -22,9 +22,9 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad import com.android.systemui.statusbar.chips.StatusBarChipsLog +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor -import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -44,25 +44,17 @@ constructor( @StatusBarChipsLog private val logger: LogBuffer, ) { val ongoingCallState: StateFlow<OngoingCallModel> = - (if (StatusBarChipsModernization.isEnabled) - ongoingCallInteractor.ongoingCallState - else - repository.ongoingCallState) + (if (StatusBarChipsModernization.isEnabled) { + ongoingCallInteractor.ongoingCallState + } else { + repository.ongoingCallState + }) .onEach { - logger.log( - TAG, - LogLevel.INFO, - { str1 = it::class.simpleName }, - { "State: $str1" } - ) + logger.log(TAG, LogLevel.INFO, { str1 = it::class.simpleName }, { "State: $str1" }) } - .stateIn( - scope, - SharingStarted.Lazily, - OngoingCallModel.NoCall - ) + .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall) companion object { private val TAG = "OngoingCall".pad() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt index e3be95373698..402881d438dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt @@ -29,10 +29,10 @@ object NewStatusBarIcons { val token: FlagToken get() = FlagToken(FLAG_NAME, isEnabled) - /** Is the refactor enabled */ + /** Is the refactor enabled. Dependency on [StatusBarRootModernization] */ @JvmStatic inline val isEnabled - get() = Flags.newStatusBarIcons() + get() = Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt index 383227d2b3aa..ab40582afc10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt @@ -21,6 +21,7 @@ import android.view.ViewGroup import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.TransitionAnimator +import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil @@ -157,8 +158,8 @@ class NotificationTransitionAnimatorController( private val headsUpNotificationRow: ExpandableNotificationRow? get() { - val summaryEntry = notificationEntry.parent?.summary - + val pipelineParent = notificationEntry.parent + val summaryEntry = (pipelineParent as? GroupEntry)?.summary return when { headsUpManager.isHeadsUpEntry(notificationKey) -> notification summaryEntry == null -> null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java index c6775d6dc051..31bcf2bc819b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java @@ -26,6 +26,7 @@ import com.android.internal.util.ContrastColorUtil; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.util.Compile; @@ -80,7 +81,7 @@ public class NotificationUtils { private static final boolean INCLUDE_HASH_CODE_IN_LIST_ENTRY_LOG_KEY = false; /** Get the notification key, reformatted for logging, for the (optional) entry */ - public static String logKey(ListEntry entry) { + public static String logKey(PipelineEntry entry) { if (entry == null) { return "null"; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt index 432bac49fa9b..2c29e30f9660 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt @@ -17,10 +17,10 @@ package com.android.systemui.statusbar.notification import android.service.notification.StatusBarNotification -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry /** Get the notification key, reformatted for logging, for the (optional) entry */ -val ListEntry?.logKey: String? +val PipelineEntry?.logKey: String? get() = this?.let { NotificationUtils.logKey(it) } /** Get the notification key, reformatted for logging, for the (optional) sbn */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java index 0e3f103c152e..35a28288c631 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java @@ -21,29 +21,62 @@ import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; +import android.app.Notification; +import android.content.Context; +import android.os.Build; + import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.systemui.statusbar.notification.icon.IconPack; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import java.util.List; /** - * Abstract class to represent notification section bundled by AI. + * Class to represent notifications bundled by classification. */ public class BundleEntry extends PipelineEntry { - private final String mKey; private final BundleEntryAdapter mEntryAdapter; // TODO (b/389839319): implement the row private ExpandableNotificationRow mRow; public BundleEntry(String key) { - mKey = key; + super(key); mEntryAdapter = new BundleEntryAdapter(); } + @Nullable + @Override + public NotificationEntry getRepresentativeEntry() { + return null; + } + + @Nullable + @Override + public NotifSection getSection() { + return null; + } + + @Override + public int getSectionIndex() { + return 0; + } + + @Nullable + @Override + public PipelineEntry getParent() { + return null; + } + + @Override + public boolean wasAttachedInPreviousPass() { + return false; + } + @VisibleForTesting public BundleEntryAdapter getEntryAdapter() { return mEntryAdapter; @@ -79,6 +112,43 @@ public class BundleEntry extends PipelineEntry { public EntryAdapter getGroupRoot() { return this; } + + @Override + public boolean isClearable() { + // TODO(b/394483200): check whether all of the children are clearable, when implemented + return true; + } + + @Override + public int getTargetSdk() { + return Build.VERSION_CODES.CUR_DEVELOPMENT; + } + + @Override + public String getSummarization() { + return null; + } + + @Override + public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) { + return Notification.COLOR_DEFAULT; + } + + @Override + public boolean canPeek() { + return false; + } + + @Override + public long getWhen() { + return 0; + } + + @Override + public IconPack getIcons() { + // TODO(b/396446620): implement bundle icons + return null; + } } public static final List<BundleEntry> ROOT_BUNDLES = List.of( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java index 4df81c97e21e..6431cacf2107 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.notification.collection; +import android.content.Context; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.systemui.statusbar.notification.icon.IconPack; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; /** @@ -59,9 +62,49 @@ public interface EntryAdapter { EntryAdapter getGroupRoot(); /** + * @return whether the row can be removed with the 'Clear All' action + */ + boolean isClearable(); + + /** * Returns whether the entry is attached to the current shade list */ default boolean isAttached() { return getParent() != null; } + + /** + * Returns the target sdk of the package that owns this entry. + */ + int getTargetSdk(); + + /** + * Returns the summarization for this entry, if there is one + */ + @Nullable String getSummarization(); + + /** + * Performs any steps needed to set or reset data before an inflation or reinflation. + */ + default void prepareForInflation() {} + + /** + * Gets a color that would have sufficient contrast on the given background color. + */ + int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor); + + /** + * Whether this entry can peek on screen as a heads up view + */ + boolean canPeek(); + + /** + * Returns the visible 'time', in milliseconds, of the entry + */ + long getWhen(); + + /** + * Retrieves the pack of icons associated with this entry + */ + IconPack getIcons(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java index 918843cedd2e..8726e83d7ddf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java @@ -75,6 +75,7 @@ public class GroupEntry extends ListEntry { return mChildren; } + // TODO(b/394483200) Change ROOT_ENTRY to PipelineEntry public static final GroupEntry ROOT_ENTRY = new GroupEntry("<root>", 0); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt index b5fce4163bb0..4a1b9568c714 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt @@ -21,14 +21,14 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter /** - * Stores the state that [ShadeListBuilder] assigns to this [ListEntry] + * Stores the state that [ShadeListBuilder] assigns to this [PipelineEntry] */ data class ListAttachState private constructor( /** * Null if not attached to the current shade list. If top-level, then the shade list root. If * part of a group, then that group's GroupEntry. */ - var parent: GroupEntry?, + var parent: PipelineEntry?, /** * The section that this ListEntry was sorted into. If the child of the group, this will be the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java index f6a572ec6ce6..60b75b1fd8c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java @@ -39,14 +39,14 @@ public class ListDumper { * entry to be in its current state (ie: filter, lifeExtender) */ public static String dumpTree( - List<ListEntry> entries, + List<PipelineEntry> entries, NotificationInteractionTracker interactionTracker, boolean includeRecordKeeping, String indent) { StringBuilder sb = new StringBuilder(); final String childEntryIndent = indent + INDENT; for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) { - ListEntry entry = entries.get(topEntryIndex); + PipelineEntry entry = entries.get(topEntryIndex); dumpEntry(entry, Integer.toString(topEntryIndex), indent, @@ -106,7 +106,7 @@ public class ListDumper { } private static void dumpEntry( - ListEntry entry, + PipelineEntry entry, String index, String indent, StringBuilder sb, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index c8e3be4e57b4..697d0a06cf9d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -21,28 +21,18 @@ import android.annotation.UptimeMillisLong; import androidx.annotation.Nullable; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; - /** * Abstract superclass for top-level entries, i.e. things that can appear in the final notification * list shown to users. In practice, this means either GroupEntries or NotificationEntries. */ public abstract class ListEntry extends PipelineEntry { - private final String mKey; private final long mCreationTime; - private final ListAttachState mPreviousAttachState = ListAttachState.create(); - private final ListAttachState mAttachState = ListAttachState.create(); - protected ListEntry(String key, long creationTime) { - mKey = key; + super(key); mCreationTime = creationTime; } - public String getKey() { - return mKey; - } - /** * The SystemClock.uptimeMillis() when this object was created. In general, this means the * moment when NotificationManager notifies our listener about the existence of this entry. @@ -64,34 +54,22 @@ public abstract class ListEntry extends PipelineEntry { */ public abstract @Nullable NotificationEntry getRepresentativeEntry(); - @Nullable public GroupEntry getParent() { + @Nullable public PipelineEntry getParent() { return mAttachState.getParent(); } - void setParent(@Nullable GroupEntry parent) { + void setParent(@Nullable PipelineEntry parent) { mAttachState.setParent(parent); } - @Nullable public GroupEntry getPreviousParent() { + @Nullable public PipelineEntry getPreviousParent() { return mPreviousAttachState.getParent(); } - @Nullable public NotifSection getSection() { - return mAttachState.getSection(); - } - public int getSectionIndex() { return mAttachState.getSection() != null ? mAttachState.getSection().getIndex() : -1; } - ListAttachState getAttachState() { - return mAttachState; - } - - ListAttachState getPreviousAttachState() { - return mPreviousAttachState; - } - /** * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a * fresh attach state (all entries will be null/default-initialized). @@ -100,11 +78,4 @@ public abstract class ListEntry extends PipelineEntry { mPreviousAttachState.clone(mAttachState); mAttachState.reset(); } - - /** - * True if this entry was attached in the last pass, else false. - */ - public boolean wasAttachedInPreviousPass() { - return getPreviousAttachState().getParent() != null; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 90f9525c7683..e5b72d459069 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -78,6 +78,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel; import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.PriorityBucket; import com.android.systemui.util.ListenerSet; @@ -304,11 +305,54 @@ public final class NotificationEntry extends ListEntry { if (isTopLevelEntry() || getParent() == null) { return null; } - if (NotificationEntry.this.getParent().getSummary() != null) { - return NotificationEntry.this.getParent().getSummary().mEntryAdapter; + if (NotificationEntry.this.getParent() instanceof GroupEntry parentGroupEntry) { + if (parentGroupEntry.getSummary() != null) { + return parentGroupEntry.getSummary().mEntryAdapter; + } } return null; } + + @Override + public boolean isClearable() { + return NotificationEntry.this.isClearable(); + } + + @Override + public int getTargetSdk() { + return NotificationEntry.this.targetSdk; + } + + @Override + public String getSummarization() { + return getRanking().getSummarization(); + } + + @Override + public void prepareForInflation() { + getSbn().clearPackageContext(); + } + + @Override + public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) { + return NotificationEntry.this.getContrastedColor( + context, isLowPriority, backgroundColor); + } + + @Override + public boolean canPeek() { + return isStickyAndNotDemoted(); + } + + @Override + public long getWhen() { + return getSbn().getNotification().getWhen(); + } + + @Override + public IconPack getIcons() { + return NotificationEntry.this.getIcons(); + } } public EntryAdapter getEntryAdapter() { @@ -546,7 +590,7 @@ public final class NotificationEntry extends ListEntry { * Get the children that are actually attached to this notification's row. * * TODO: Seems like most callers here should probably be using - * {@link GroupMembershipManager#getChildren(ListEntry)} + * {@link GroupMembershipManager#getChildren(PipelineEntry)} */ public @Nullable List<NotificationEntry> getAttachedNotifChildren() { if (row == null) { @@ -580,6 +624,7 @@ public final class NotificationEntry extends ListEntry { } public boolean hasFinishedInitialization() { + NotificationBundleUi.assertInLegacyMode(); return initializationTime != -1 && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY; } @@ -663,10 +708,12 @@ public final class NotificationEntry extends ListEntry { } public void resetInitializationTime() { + NotificationBundleUi.assertInLegacyMode(); initializationTime = -1; } public void setInitializationTime(long time) { + NotificationBundleUi.assertInLegacyMode(); if (initializationTime == -1) { initializationTime = time; } @@ -683,9 +730,13 @@ public final class NotificationEntry extends ListEntry { * @return {@code true} if we are a media notification */ public boolean isMediaNotification() { - if (row == null) return false; + if (NotificationBundleUi.isEnabled()) { + return getSbn().getNotification().isMediaNotification(); + } else { + if (row == null) return false; - return row.isMediaRow(); + return row.isMediaRow(); + } } public boolean containsCustomViews() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java index c5a479180329..78652ccda1d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java @@ -16,8 +16,74 @@ package com.android.systemui.statusbar.notification.collection; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; + /** * Class to represent a notification, group, or bundle in the pipeline. */ public abstract class PipelineEntry { + + final String mKey; + final ListAttachState mAttachState = ListAttachState.create(); + final ListAttachState mPreviousAttachState = ListAttachState.create(); + + public PipelineEntry(String key) { + this.mKey = key; + } + + /** + * Key of the representative entry. + */ + public @NonNull String getKey() { + return mKey; + } + + /** + * @return The representative NotificationEntry: + * for NotificationEntry, return itself + * for GroupEntry, return the summary NotificationEntry, or null if it does not exist + * for BundleEntry, return null + */ + public abstract @Nullable NotificationEntry getRepresentativeEntry(); + + /** + * @return NotifSection that ShadeListBuilder assigned to this PipelineEntry. + */ + @Nullable public NotifSection getSection() { + return mAttachState.getSection(); + } + + /** + * @return True if this entry was attached in the last pass, else false. + */ + public boolean wasAttachedInPreviousPass() { + return getPreviousAttachState().getParent() != null; + } + + /** + * @return Index of section assigned to this entry. + */ + public abstract int getSectionIndex(); + + /** + * @return Parent PipelineEntry + */ + public abstract @Nullable PipelineEntry getParent(); + + /** + * @return Current state that ShadeListBuilder assigned to this PipelineEntry. + */ + final ListAttachState getAttachState() { + return mAttachState; + } + + /** + * @return Previous state that ShadeListBuilder assigned to this PipelineEntry. + */ + final ListAttachState getPreviousAttachState() { + return mPreviousAttachState; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 9c1d0735a65b..bb84ab8f421a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -67,6 +67,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.util.Assert; import com.android.systemui.util.NamedListenerSet; @@ -99,15 +100,15 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { private final DumpManager mDumpManager; // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated // TODO replace temp with collection pool for readability - private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>(); + private final ArrayList<PipelineEntry> mTempSectionMembers = new ArrayList<>(); private NotifPipelineFlags mFlags; private final boolean mAlwaysLogList; - private List<ListEntry> mNotifList = new ArrayList<>(); - private List<ListEntry> mNewNotifList = new ArrayList<>(); + private List<PipelineEntry> mNotifList = new ArrayList<>(); + private List<PipelineEntry> mNewNotifList = new ArrayList<>(); private final SemiStableSort mSemiStableSort = new SemiStableSort(); - private final StableOrder<ListEntry> mStableOrder = this::getStableOrderRank; + private final StableOrder<PipelineEntry> mStableOrder = this::getStableOrderRank; private final PipelineState mPipelineState = new PipelineState(); private final Map<String, GroupEntry> mGroups = new ArrayMap<>(); private Collection<NotificationEntry> mAllEntries = Collections.emptyList(); @@ -132,8 +133,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { mOnBeforeRenderListListeners = new NamedListenerSet<>(); @Nullable private OnRenderListListener mOnRenderListListener; - private List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); - private List<ListEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList); + private List<PipelineEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); + private List<PipelineEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList); private final NotifPipelineChoreographer mChoreographer; private int mConsecutiveReentrantRebuilds = 0; @@ -307,7 +308,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { } } - List<ListEntry> getShadeList() { + List<PipelineEntry> getShadeList() { Assert.isMainThread(); // NOTE: Accessing this method when the pipeline is running is generally going to provide // incorrect results, and indicates a poorly behaved component of the pipeline. @@ -492,7 +493,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Trace.beginSection("ShadeListBuilder.notifySectionEntriesUpdated"); mTempSectionMembers.clear(); for (NotifSection section : mNotifSections) { - for (ListEntry entry : mNotifList) { + for (PipelineEntry entry : mNotifList) { if (section == entry.getSection()) { mTempSectionMembers.add(entry); } @@ -513,11 +514,11 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { */ private void applyNewNotifList() { mNotifList.clear(); - List<ListEntry> emptyList = mNotifList; + List<PipelineEntry> emptyList = mNotifList; mNotifList = mNewNotifList; mNewNotifList = emptyList; - List<ListEntry> readOnlyNotifList = mReadOnlyNotifList; + List<PipelineEntry> readOnlyNotifList = mReadOnlyNotifList; mReadOnlyNotifList = mReadOnlyNewNotifList; mReadOnlyNewNotifList = readOnlyNotifList; } @@ -537,12 +538,12 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { } private void filterNotifs( - Collection<? extends ListEntry> entries, - List<ListEntry> out, + Collection<? extends PipelineEntry> entries, + List<PipelineEntry> out, List<NotifFilter> filters) { Trace.beginSection("ShadeListBuilder.filterNotifs"); final long now = mSystemClock.uptimeMillis(); - for (ListEntry entry : entries) { + for (PipelineEntry entry : entries) { if (entry instanceof GroupEntry) { final GroupEntry groupEntry = (GroupEntry) entry; @@ -575,11 +576,11 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Trace.endSection(); } - private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) { + private void groupNotifs(List<PipelineEntry> entries, List<PipelineEntry> out) { Trace.beginSection("ShadeListBuilder.groupNotifs"); - for (ListEntry listEntry : entries) { + for (PipelineEntry PipelineEntry : entries) { // since grouping hasn't happened yet, all notifs are NotificationEntries - NotificationEntry entry = (NotificationEntry) listEntry; + NotificationEntry entry = (NotificationEntry) PipelineEntry; if (entry.getSbn().isGroup()) { final String topLevelKey = entry.getSbn().getGroupKey(); @@ -630,14 +631,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Trace.endSection(); } - private void stabilizeGroupingNotifs(List<ListEntry> topLevelList) { + private void stabilizeGroupingNotifs(List<PipelineEntry> topLevelList) { if (getStabilityManager().isEveryChangeAllowed()) { return; } Trace.beginSection("ShadeListBuilder.stabilizeGroupingNotifs"); for (int i = 0; i < topLevelList.size(); i++) { - final ListEntry tle = topLevelList.get(i); + final PipelineEntry tle = topLevelList.get(i); if (tle instanceof GroupEntry) { // maybe put children back into their old group (including moving back to top-level) GroupEntry groupEntry = (GroupEntry) tle; @@ -666,13 +667,13 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { /** * Returns true if the group change was suppressed, else false */ - private boolean maybeSuppressGroupChange(NotificationEntry entry, List<ListEntry> out) { - final GroupEntry prevParent = entry.getPreviousAttachState().getParent(); + private boolean maybeSuppressGroupChange(NotificationEntry entry, List<PipelineEntry> out) { + final PipelineEntry prevParent = entry.getPreviousAttachState().getParent(); if (prevParent == null) { // New entries are always allowed. return false; } - final GroupEntry assignedParent = entry.getParent(); + final PipelineEntry assignedParent = entry.getParent(); if (prevParent == assignedParent) { // Nothing to change. return false; @@ -690,21 +691,22 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { entry.setParent(prevParent); if (prevParent == ROOT_ENTRY) { out.add(entry); - } else { - prevParent.addChild(entry); + } else if (prevParent instanceof GroupEntry) { + ((GroupEntry) prevParent).addChild(entry); if (!mGroups.containsKey(prevParent.getKey())) { - mGroups.put(prevParent.getKey(), prevParent); + mGroups.put(prevParent.getKey(), (GroupEntry) prevParent); } } + // TODO(b/394483200): Revisit group stability for BundleEntry return true; } return false; } - private void promoteNotifs(List<ListEntry> list) { + private void promoteNotifs(List<PipelineEntry> list) { Trace.beginSection("ShadeListBuilder.promoteNotifs"); for (int i = 0; i < list.size(); i++) { - final ListEntry tle = list.get(i); + final PipelineEntry tle = list.get(i); if (tle instanceof GroupEntry) { final GroupEntry group = (GroupEntry) tle; @@ -724,7 +726,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Trace.endSection(); } - private void pruneIncompleteGroups(List<ListEntry> shadeList) { + private void pruneIncompleteGroups(List<PipelineEntry> shadeList) { Trace.beginSection("ShadeListBuilder.pruneIncompleteGroups"); // Any group which lost a child on this run to stability is exempt from being pruned or // having its summary promoted, regardless of how many children it has @@ -741,7 +743,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { // Iterate backwards, so that we can remove elements without affecting indices of // yet-to-be-accessed entries. for (int i = shadeList.size() - 1; i >= 0; i--) { - final ListEntry tle = shadeList.get(i); + final PipelineEntry tle = shadeList.get(i); if (tle instanceof GroupEntry) { final GroupEntry group = (GroupEntry) tle; @@ -792,7 +794,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Trace.endSection(); } - private void pruneGroupAtIndexAndPromoteSummary(List<ListEntry> shadeList, + private void pruneGroupAtIndexAndPromoteSummary(List<PipelineEntry> shadeList, GroupEntry group, int index) { // Validate that the group has no children checkArgument(group.getChildren().isEmpty(), "group should have no children"); @@ -800,7 +802,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { NotificationEntry summary = group.getSummary(); summary.setParent(ROOT_ENTRY); // The list may be sorted; replace the group with the summary, in its place - ListEntry oldEntry = shadeList.set(index, summary); + PipelineEntry oldEntry = shadeList.set(index, summary); // Validate that the replaced entry was the group entry checkState(oldEntry == group); @@ -811,10 +813,10 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { "SUMMARY with no children @ " + mPipelineState.getStateName()); } - private void pruneGroupAtIndexAndPromoteAnyChildren(List<ListEntry> shadeList, + private void pruneGroupAtIndexAndPromoteAnyChildren(List<PipelineEntry> shadeList, GroupEntry group, int index) { // REMOVE the GroupEntry at this index - ListEntry oldEntry = shadeList.remove(index); + PipelineEntry oldEntry = shadeList.remove(index); // Validate that the replaced entry was the group entry checkState(oldEntry == group); @@ -867,14 +869,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { * top level (ungrouped) notifications. */ @NonNull - private Set<String> getGroupsWithChildrenLostToStability(List<ListEntry> shadeList) { + private Set<String> getGroupsWithChildrenLostToStability(List<PipelineEntry> shadeList) { if (getStabilityManager().isEveryChangeAllowed()) { return Collections.emptySet(); } ArraySet<String> groupsWithChildrenLostToStability = new ArraySet<>(); for (int i = 0; i < shadeList.size(); i++) { - final ListEntry tle = shadeList.get(i); - final GroupEntry suppressedParent = + final PipelineEntry tle = shadeList.get(i); + final PipelineEntry suppressedParent = tle.getAttachState().getSuppressedChanges().getParent(); if (suppressedParent != null) { // This top-level-entry was supposed to be attached to this group, @@ -891,9 +893,10 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { * * These groups will be exempt from appearing without any children. */ - private void addGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList, Set<String> out) { + private void addGroupsWithChildrenLostToPromotion(List<PipelineEntry> shadeList, + Set<String> out) { for (int i = 0; i < shadeList.size(); i++) { - final ListEntry tle = shadeList.get(i); + final PipelineEntry tle = shadeList.get(i); if (tle.getAttachState().getPromoter() != null) { // This top-level-entry was part of a group, but was promoted out of it. final String groupKey = tle.getRepresentativeEntry().getSbn().getGroupKey(); @@ -909,7 +912,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { * These groups will be exempt from appearing without any children. */ private void addGroupsWithChildrenLostToFiltering(Set<String> out) { - for (ListEntry tle : mAllEntries) { + for (PipelineEntry tle : mAllEntries) { StatusBarNotification sbn = tle.getRepresentativeEntry().getSbn(); if (sbn.isGroup() && !sbn.getNotification().isGroupSummary() @@ -920,14 +923,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { } /** - * If a ListEntry was added to the shade list and then later removed (e.g. because it was a + * If a PipelineEntry was added to the shade list and then later removed (e.g. because it was a * group that was broken up), this method will erase any bookkeeping traces of that addition * and/or check that they were already erased. * * Before calling this method, the entry must already have been removed from its parent. If * it's a group, its summary must be null and its children must be empty. */ - private void annulAddition(ListEntry entry, List<ListEntry> shadeList) { + private void annulAddition(PipelineEntry entry, List<PipelineEntry> shadeList) { // This function does very little, but if any of its assumptions are violated (and it has a // lot of them), it will put the system into an inconsistent state. So we check all of them @@ -955,9 +958,11 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { throw new IllegalStateException( "Cannot nullify group " + ge.getKey() + ": still has children"); } - } else if (entry instanceof NotificationEntry) { - if (entry == entry.getParent().getSummary() - || entry.getParent().getChildren().contains(entry)) { + } else if (entry instanceof NotificationEntry + && entry.getParent() instanceof GroupEntry) { + GroupEntry parentGroupEntry = (GroupEntry) entry.getParent(); + if (entry == parentGroupEntry.getSummary() + || parentGroupEntry.getChildren().contains(entry)) { throw new IllegalStateException("Cannot nullify addition of child " + entry.getKey() + ": it's still attached to its parent."); } @@ -972,14 +977,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { * This can happen if the entry is removed from a group that was broken up or if the entry was * filtered out during any of the filtering steps. */ - private void annulAddition(ListEntry entry) { + private void annulAddition(PipelineEntry entry) { entry.getAttachState().detach(); } private void assignSections() { Trace.beginSection("ShadeListBuilder.assignSections"); // Assign sections to top-level elements and their children - for (ListEntry entry : mNotifList) { + for (PipelineEntry entry : mNotifList) { NotifSection section = applySections(entry); if (entry instanceof GroupEntry) { GroupEntry parent = (GroupEntry) entry; @@ -1000,7 +1005,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { private void sortWithSemiStableSort() { // Sort each group's children boolean allSorted = true; - for (ListEntry entry : mNotifList) { + for (PipelineEntry entry : mNotifList) { if (entry instanceof GroupEntry) { GroupEntry parent = (GroupEntry) entry; allSorted &= sortGroupChildren(parent.getRawChildren()); @@ -1009,7 +1014,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { // Sort each section within the top level list mNotifList.sort(mTopLevelComparator); if (!getStabilityManager().isEveryChangeAllowed()) { - for (List<ListEntry> subList : getSectionSubLists(mNotifList)) { + for (List<PipelineEntry> subList : getSectionSubLists(mNotifList)) { allSorted &= mSemiStableSort.stabilizeTo(subList, mStableOrder, mNewNotifList); } applyNewNotifList(); @@ -1021,7 +1026,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { } } - private Iterable<List<ListEntry>> getSectionSubLists(List<ListEntry> entries) { + private Iterable<List<PipelineEntry>> getSectionSubLists(List<PipelineEntry> entries) { return ShadeListBuilderHelper.INSTANCE.getSectionSubLists(entries); } @@ -1056,12 +1061,12 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { /** * Assign the index of each notification relative to the total order */ - private void assignIndexes(List<ListEntry> notifList) { + private void assignIndexes(List<PipelineEntry> notifList) { if (notifList.size() == 0) return; NotifSection currentSection = requireNonNull(notifList.get(0).getSection()); int sectionMemberIndex = 0; for (int i = 0; i < notifList.size(); i++) { - final ListEntry entry = notifList.get(i); + final PipelineEntry entry = notifList.get(i); NotifSection section = requireNonNull(entry.getSection()); if (section.getIndex() != currentSection.getIndex()) { sectionMemberIndex = 0; @@ -1098,7 +1103,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Trace.endSection(); } - private void logAttachStateChanges(ListEntry entry) { + private void logAttachStateChanges(PipelineEntry entry) { final ListAttachState curr = entry.getAttachState(); final ListAttachState prev = entry.getPreviousAttachState(); @@ -1114,8 +1119,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent()); } - GroupEntry currSuppressedParent = curr.getSuppressedChanges().getParent(); - GroupEntry prevSuppressedParent = prev.getSuppressedChanges().getParent(); + PipelineEntry currSuppressedParent = curr.getSuppressedChanges().getParent(); + PipelineEntry prevSuppressedParent = prev.getSuppressedChanges().getParent(); if (currSuppressedParent != null && (prevSuppressedParent == null || !prevSuppressedParent.getKey().equals(currSuppressedParent.getKey()))) { mLogger.logParentChangeSuppressedStarted( @@ -1209,7 +1214,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { @Nullable private NotifComparator getSectionComparator( - @NonNull ListEntry o1, @NonNull ListEntry o2) { + @NonNull PipelineEntry o1, @NonNull PipelineEntry o2) { final NotifSection section = o1.getSection(); if (section != o2.getSection()) { throw new RuntimeException("Entry ordering should only be done within sections"); @@ -1220,7 +1225,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { return null; } - private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> { + private final Comparator<PipelineEntry> mTopLevelComparator = (o1, o2) -> { int cmp = Integer.compare( o1.getSectionIndex(), o2.getSectionIndex()); @@ -1263,7 +1268,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { }; @Nullable - private Integer getStableOrderRank(ListEntry entry) { + private Integer getStableOrderRank(PipelineEntry entry) { if (getStabilityManager().isEntryReorderingAllowed(entry)) { // let the stability manager constrain or allow reordering return null; @@ -1282,7 +1287,13 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { entry.getAttachState().setExcludingFilter(filter); if (filter != null) { // notification is removed from the list, so we reset its initialization time - entry.resetInitializationTime(); + if (NotificationBundleUi.isEnabled()) { + if (entry.getRow() != null) { + entry.getRow().resetInitializationTime(); + } + } else { + entry.resetInitializationTime(); + } } return filter != null; } @@ -1316,7 +1327,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { return null; } - private NotifSection applySections(ListEntry entry) { + private NotifSection applySections(PipelineEntry entry) { final NotifSection newSection = findSection(entry); final ListAttachState prevAttachState = entry.getPreviousAttachState(); @@ -1339,7 +1350,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { return finalSection; } - private void setEntrySection(ListEntry entry, NotifSection finalSection) { + private void setEntrySection(PipelineEntry entry, NotifSection finalSection) { entry.getAttachState().setSection(finalSection); NotificationEntry representativeEntry = entry.getRepresentativeEntry(); if (representativeEntry != null) { @@ -1351,7 +1362,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { } @NonNull - private NotifSection findSection(ListEntry entry) { + private NotifSection findSection(PipelineEntry entry) { for (int i = 0; i < mNotifSections.size(); i++) { NotifSection section = mNotifSections.get(i); if (section.getSectioner().isInSection(entry)) { @@ -1421,10 +1432,10 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { mChoreographer.schedule(); } - private static int countChildren(List<ListEntry> entries) { + private static int countChildren(List<PipelineEntry> entries) { int count = 0; for (int i = 0; i < entries.size(); i++) { - final ListEntry entry = entries.get(i); + final PipelineEntry entry = entries.get(i); if (entry instanceof GroupEntry) { count += ((GroupEntry) entry).getChildren().size(); } @@ -1432,7 +1443,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { return count; } - private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) { + private void dispatchOnBeforeTransformGroups(List<PipelineEntry> entries) { Trace.beginSection("ShadeListBuilder.dispatchOnBeforeTransformGroups"); mOnBeforeTransformGroupsListeners.forEachTraced(listener -> { listener.onBeforeTransformGroups(entries); @@ -1440,7 +1451,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Trace.endSection(); } - private void dispatchOnBeforeSort(List<ListEntry> entries) { + private void dispatchOnBeforeSort(List<PipelineEntry> entries) { Trace.beginSection("ShadeListBuilder.dispatchOnBeforeSort"); mOnBeforeSortListeners.forEachTraced(listener -> { listener.onBeforeSort(entries); @@ -1448,7 +1459,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Trace.endSection(); } - private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) { + private void dispatchOnBeforeFinalizeFilter(List<PipelineEntry> entries) { Trace.beginSection("ShadeListBuilder.dispatchOnBeforeFinalizeFilter"); mOnBeforeFinalizeFilterListeners.forEachTraced(listener -> { listener.onBeforeFinalizeFilter(entries); @@ -1456,7 +1467,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Trace.endSection(); } - private void dispatchOnBeforeRenderList(List<ListEntry> entries) { + private void dispatchOnBeforeRenderList(List<PipelineEntry> entries) { Trace.beginSection("ShadeListBuilder.dispatchOnBeforeRenderList"); mOnBeforeRenderListListeners.forEachTraced(listener -> { listener.onBeforeRenderList(entries); @@ -1501,13 +1512,13 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { * @param entries A read-only view into the current notif list. Note that this list is * backed by the live list and will change in response to new pipeline runs. */ - void onRenderList(@NonNull List<ListEntry> entries); + void onRenderList(@NonNull List<PipelineEntry> entries); } private static final NotifSectioner DEFAULT_SECTIONER = new NotifSectioner("UnknownSection", NotificationPriorityBucketKt.BUCKET_UNKNOWN) { @Override - public boolean isInSection(ListEntry entry) { + public boolean isInSection(PipelineEntry entry) { return true; } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt index 584563b6c388..c9429547a518 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.collection import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection /** - * Stores the suppressed state that [ShadeListBuilder] assigned to this [ListEntry] before the + * Stores the suppressed state that [ShadeListBuilder] assigned to this [PipelineEntry] before the * VisualStabilityManager suppressed group and section changes. */ data class SuppressedAttachState private constructor( @@ -35,7 +35,7 @@ data class SuppressedAttachState private constructor( * - Root if suppressing group change to top-level * - GroupEntry if suppressing group change to a different group */ - var parent: GroupEntry?, + var parent: PipelineEntry?, /** * Whether the ListEntry would have been pruned had its group change not been suppressed. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt index 244c5946c21e..e6d5f4120a20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt @@ -20,7 +20,7 @@ import android.app.NotificationChannel.NEWS_ID import android.app.NotificationChannel.PROMOTIONS_ID import android.app.NotificationChannel.RECS_ID import android.app.NotificationChannel.SOCIAL_MEDIA_ID -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner @@ -48,7 +48,7 @@ class BundleCoordinator @Inject constructor( val newsSectioner = object : NotifSectioner("News", BUCKET_NEWS) { - override fun isInSection(entry: ListEntry): Boolean { + override fun isInSection(entry: PipelineEntry): Boolean { return entry.representativeEntry?.channel?.id == NEWS_ID } @@ -59,7 +59,7 @@ class BundleCoordinator @Inject constructor( val socialSectioner = object : NotifSectioner("Social", BUCKET_SOCIAL) { - override fun isInSection(entry: ListEntry): Boolean { + override fun isInSection(entry: PipelineEntry): Boolean { return entry.representativeEntry?.channel?.id == SOCIAL_MEDIA_ID } @@ -70,7 +70,7 @@ class BundleCoordinator @Inject constructor( val recsSectioner = object : NotifSectioner("Recommendations", BUCKET_RECS) { - override fun isInSection(entry: ListEntry): Boolean { + override fun isInSection(entry: PipelineEntry): Boolean { return entry.representativeEntry?.channel?.id == RECS_ID } @@ -81,7 +81,7 @@ class BundleCoordinator @Inject constructor( val promoSectioner = object : NotifSectioner("Promotions", BUCKET_PROMO) { - override fun isInSection(entry: ListEntry): Boolean { + override fun isInSection(entry: PipelineEntry): Boolean { return entry.representativeEntry?.channel?.id == PROMOTIONS_ID } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java index 9df4bf4af4e8..afba85b49c30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java @@ -25,6 +25,7 @@ import androidx.annotation.Nullable; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; @@ -96,7 +97,7 @@ public class ColorizedFgsCoordinator implements Coordinator { private final NotifSectioner mNotifSectioner = new NotifSectioner("ColorizedSectioner", NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) { @Override - public boolean isInSection(ListEntry entry) { + public boolean isInSection(PipelineEntry entry) { NotificationEntry notificationEntry = entry.getRepresentativeEntry(); if (notificationEntry != null) { return isRichOngoing(notificationEntry); @@ -117,7 +118,7 @@ public class ColorizedFgsCoordinator implements Coordinator { private final NotifComparator mOngoingComparator = new NotifComparator( "OngoingComparator") { @Override - public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) { + public int compare(@NonNull PipelineEntry o1, @NonNull PipelineEntry o2) { return Integer.compare( getSortKey(o1.getRepresentativeEntry()), getSortKey(o2.getRepresentativeEntry()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index af2c1979ff77..248b5286803f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -16,7 +16,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag @@ -61,6 +62,7 @@ class ConversationCoordinator @Inject constructor( originalGroup == null -> null originalGroup == promoted.parent -> null originalGroup.parent == null -> null + originalGroup !is GroupEntry -> summary.key originalGroup.summary != summary -> null originalGroup.children.any { it.channel == summary.channel } -> null else -> summary.key @@ -74,7 +76,7 @@ class ConversationCoordinator @Inject constructor( override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean { val shouldPromote = entry.channel?.isImportantConversation == true if (shouldPromote) { - val summary = entry.parent?.summary + val summary = (entry.parent as? GroupEntry)?.summary if (summary != null && entry.channel == summary.channel) { promotedEntriesToSummaryOfSameChannel[entry] = summary } @@ -85,14 +87,14 @@ class ConversationCoordinator @Inject constructor( val priorityPeopleSectioner = object : NotifSectioner("Priority People", BUCKET_PRIORITY_PEOPLE) { - override fun isInSection(entry: ListEntry): Boolean { + override fun isInSection(entry: PipelineEntry): Boolean { return getPeopleType(entry) == TYPE_IMPORTANT_PERSON } } // TODO(b/330193582): Rename to just "People" val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) { - override fun isInSection(entry: ListEntry): Boolean { + override fun isInSection(entry: PipelineEntry): Boolean { if (SortBySectionTimeFlag.isEnabled) { return highPriorityProvider.isHighPriorityConversation(entry) || isConversation(entry) @@ -111,7 +113,7 @@ class ConversationCoordinator @Inject constructor( val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) { // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting. // All remaining conversations must be silent. - override fun isInSection(entry: ListEntry): Boolean { + override fun isInSection(entry: PipelineEntry): Boolean { SortBySectionTimeFlag.assertInLegacyMode() return isConversation(entry) } @@ -132,17 +134,17 @@ class ConversationCoordinator @Inject constructor( pipeline.addOnBeforeRenderListListener(onBeforeRenderListListener) } - private fun isConversation(entry: ListEntry): Boolean = + private fun isConversation(entry: PipelineEntry): Boolean = getPeopleType(entry) != TYPE_NON_PERSON @PeopleNotificationType - private fun getPeopleType(entry: ListEntry): Int = + private fun getPeopleType(entry: PipelineEntry): Int = entry.representativeEntry?.let { peopleNotificationIdentifier.getPeopleNotificationType(it) } ?: TYPE_NON_PERSON private val notifComparator: NotifComparator = object : NotifComparator("People") { - override fun compare(entry1: ListEntry, entry2: ListEntry): Int { + override fun compare(entry1: PipelineEntry, entry2: PipelineEntry): Int { val type1 = getPeopleType(entry1) val type2 = getPeopleType(entry2) return type2.compareTo(type1) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt index 034a4fd2af72..e4ec76c68e6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -43,12 +43,12 @@ internal constructor(private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl) d.dump("notifLiveDataStoreImpl", notifLiveDataStoreImpl) } - private fun onAfterRenderList(entries: List<ListEntry>) { + private fun onAfterRenderList(entries: List<PipelineEntry>) { val flatEntryList = flattenedEntryList(entries) notifLiveDataStoreImpl.setActiveNotifList(flatEntryList) } - private fun flattenedEntryList(entries: List<ListEntry>) = + private fun flattenedEntryList(entries: List<PipelineEntry>) = mutableListOf<NotificationEntry>().also { list -> entries.forEach { entry -> when (entry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinator.kt index 0a9dddc1c75e..6bed0cf054af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinator.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProviderImpl @@ -37,7 +37,7 @@ constructor( pipeline.addOnBeforeRenderListListener(::onBeforeRenderListListener) } - private fun onBeforeRenderListListener(entries: List<ListEntry>) { + private fun onBeforeRenderListListener(entries: List<PipelineEntry>) { val isLocked = !keyguardStateController.isUnlocked val nonDismissableEntryKeys = mutableSetOf<String>() markNonDismissibleEntries(nonDismissableEntryKeys, entries, isLocked) @@ -54,7 +54,7 @@ constructor( */ private fun markNonDismissibleEntries( markedKeys: MutableSet<String>, - entries: List<ListEntry>, + entries: List<PipelineEntry>, isLocked: Boolean ): Boolean { var anyNonDismissableEntries = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt index 02bf3b3be537..2f0701f96f28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.util.ArrayMap import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.render.NotifGroupController @@ -34,7 +34,7 @@ class GroupCountCoordinator @Inject constructor() : Coordinator { pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroup) } - private fun onBeforeFinalizeFilter(entries: List<ListEntry>) { + private fun onBeforeFinalizeFilter(entries: List<PipelineEntry>) { // save untruncated child counts to our internal map untruncatedChildCounts.clear() entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt index f253100b3661..d0411b56d92d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.util.ArrayMap import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator @@ -52,7 +52,7 @@ constructor( pipeline.addPreRenderInvalidator(invalidator) } - private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) { + private fun onBeforeFinalizeFilterListener(entries: List<PipelineEntry>) { cancelListInvalidation() notificationGroupTimes.clear() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt index b200136b1b43..9045aac6ea6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.util.ArraySet import com.android.systemui.Dumpable import com.android.systemui.dump.DumpManager -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope @@ -113,7 +113,7 @@ class GutsCoordinator @Inject constructor( } } - private fun isCurrentlyShowingGuts(entry: ListEntry) = + private fun isCurrentlyShowingGuts(entry: PipelineEntry) = notifsWithOpenGuts.contains(entry.key) private fun closeGutsAndEndLifetimeExtension(entry: NotificationEntry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index eb5a3703bcfb..611e0ef77fac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -27,7 +27,7 @@ import com.android.systemui.statusbar.chips.notification.domain.interactor.Statu import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -103,7 +103,7 @@ constructor( mNotifPipeline = pipeline mHeadsUpManager.addListener(mOnHeadsUpChangedListener) pipeline.addCollectionListener(mNotifCollectionListener) - pipeline.addOnBeforeTransformGroupsListener(::onBeforeTransformGroups) + pipeline.addOnBeforeTransformGroupsListener { onBeforeTransformGroups() } pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter) pipeline.addPromoter(mNotifPromoter) pipeline.addNotificationLifetimeExtender(mLifetimeExtender) @@ -170,7 +170,7 @@ constructor( * Once the pipeline starts running, we can look through posted entries and quickly process any * that don't have groups, and thus will never gave a group heads up edge case. */ - fun onBeforeTransformGroups(list: List<ListEntry>) { + fun onBeforeTransformGroups() { mNow = mSystemClock.currentTimeMillis() if (mPostedEntries.isEmpty()) { return @@ -191,7 +191,7 @@ constructor( * we know that stability and [NotifPromoter]s have been applied, so we can use the location of * notifications in this list to determine what kind of group heads up behavior should happen. */ - fun onBeforeFinalizeFilter(list: List<ListEntry>) = + fun onBeforeFinalizeFilter(list: List<PipelineEntry>) = mHeadsUpManager.modifyHuns { hunMutator -> // Nothing to do if there are no other adds/updates if (mPostedEntries.isEmpty()) { @@ -410,7 +410,7 @@ constructor( ) .firstOrNull() - private fun getGroupLocationsByKey(list: List<ListEntry>): Map<String, GroupLocation> = + private fun getGroupLocationsByKey(list: List<PipelineEntry>): Map<String, GroupLocation> = mutableMapOf<String, GroupLocation>().also { map -> list.forEach { topLevelEntry -> when (topLevelEntry) { @@ -833,13 +833,13 @@ constructor( val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) { - override fun isInSection(entry: ListEntry): Boolean = + override fun isInSection(entry: PipelineEntry): Boolean = // TODO: This check won't notice if a child of the group is going to HUN... isGoingToShowHunNoRetract(entry) override fun getComparator(): NotifComparator { return object : NotifComparator("HeadsUp") { - override fun compare(o1: ListEntry, o2: ListEntry): Int = + override fun compare(o1: PipelineEntry, o2: PipelineEntry): Int = mHeadsUpManager.compare(o1.representativeEntry, o2.representativeEntry) } } @@ -867,7 +867,7 @@ constructor( private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key) - private fun isEntryBinding(entry: ListEntry): Boolean { + private fun isEntryBinding(entry: PipelineEntry): Boolean { val bindingUntil = mEntriesBindingUntil[entry.key] return bindingUntil != null && bindingUntil >= mNow } @@ -875,12 +875,12 @@ constructor( /** * Whether the notification is already heads up or binding so that it can imminently heads up */ - private fun isAttemptingToShowHun(entry: ListEntry) = + private fun isAttemptingToShowHun(entry: PipelineEntry) = mHeadsUpManager.isHeadsUpEntry(entry.key) || isEntryBinding(entry) || isHeadsUpAnimatingAway(entry) - private fun isHeadsUpAnimatingAway(entry: ListEntry): Boolean { + private fun isHeadsUpAnimatingAway(entry: PipelineEntry): Boolean { if (!GroupHunAnimationFix.isEnabled) return false return entry.representativeEntry?.row?.isHeadsUpAnimatingAway ?: false } @@ -891,7 +891,7 @@ constructor( * returns `true` even if the update would (in isolation of its group) cause the heads up to be * retracted. This is important for not retracting transferred group heads ups. */ - private fun isGoingToShowHunNoRetract(entry: ListEntry) = + private fun isGoingToShowHunNoRetract(entry: PipelineEntry) = mPostedEntries[entry.key]?.calculateShouldBeHeadsUpNoRetract ?: isAttemptingToShowHun(entry) /** @@ -900,7 +900,7 @@ constructor( * strict because any update which would revoke the heads up supersedes the current heads * up/binding state. */ - private fun isGoingToShowHunStrict(entry: ListEntry) = + private fun isGoingToShowHunStrict(entry: PipelineEntry) = mPostedEntries[entry.key]?.calculateShouldBeHeadsUpStrict ?: isAttemptingToShowHun(entry) private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt index e2328497d9ea..56deb18df9ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt @@ -27,7 +27,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope @@ -176,7 +176,7 @@ constructor( } } - private fun pickOutTopUnseenNotifs(list: List<ListEntry>) { + private fun pickOutTopUnseenNotifs(list: List<PipelineEntry>) { if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return if (!minimalismEnabled) return // Only ever elevate a top unseen notification on keyguard, not even locked shade @@ -224,7 +224,7 @@ constructor( val topOngoingSectioner = object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) { - override fun isInSection(entry: ListEntry): Boolean { + override fun isInSection(entry: PipelineEntry): Boolean { if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false if (!minimalismEnabled) return false return entry.anyEntry { notificationEntry -> @@ -235,7 +235,7 @@ constructor( val topUnseenSectioner = object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) { - override fun isInSection(entry: ListEntry): Boolean { + override fun isInSection(entry: PipelineEntry): Boolean { if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false if (!minimalismEnabled) return false return entry.anyEntry { notificationEntry -> @@ -244,7 +244,7 @@ constructor( } } - private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) = + private fun PipelineEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) = when { predicate(representativeEntry) -> true this !is GroupEntry -> false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt index de6f2576ff19..660ee401b369 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt @@ -30,6 +30,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.expansionChanges +import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope @@ -313,7 +314,7 @@ constructor( unseenNotifications.contains(entry) -> false // Don't apply the filter to (non-promoted) group summaries // - summary will be pruned if necessary, depending on if children are filtered - entry.parent?.summary == entry -> false + (entry.parent as? GroupEntry)?.summary == entry -> false // Check that the entry satisfies certain characteristics that would bypass the // filter shouldIgnoreUnseenCheck(entry) -> false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 2ecce1f02ef6..20c6736b74c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -35,7 +35,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; @@ -232,9 +232,10 @@ public class PreparationCoordinator implements Coordinator { */ @Override public boolean shouldFilterOut(NotificationEntry entry, long now) { - final GroupEntry parent = requireNonNull(entry.getParent()); - Boolean isMemberOfDelayedGroup = mIsDelayedGroupCache.get(parent); - if (isMemberOfDelayedGroup == null) { + final PipelineEntry pipelineEntryParent = requireNonNull(entry.getParent()); + Boolean isMemberOfDelayedGroup = mIsDelayedGroupCache.get(pipelineEntryParent); + if (isMemberOfDelayedGroup == null && pipelineEntryParent instanceof GroupEntry) { + GroupEntry parent = (GroupEntry) pipelineEntryParent; isMemberOfDelayedGroup = shouldWaitForGroupToInflate(parent, now); mIsDelayedGroupCache.put(parent, isMemberOfDelayedGroup); } @@ -279,7 +280,7 @@ public class PreparationCoordinator implements Coordinator { } }; - private void purgeCaches(Collection<ListEntry> entries) { + private void purgeCaches(Collection<PipelineEntry> entries) { Set<String> wantedPackages = getPackages(entries); mAppIconProvider.purgeCache(wantedPackages); mNotificationIconStyleProvider.purgeCache(wantedPackages); @@ -288,9 +289,9 @@ public class PreparationCoordinator implements Coordinator { /** * Get all app packages present in {@param entries}. */ - private static @NonNull Set<String> getPackages(Collection<ListEntry> entries) { + private static @NonNull Set<String> getPackages(Collection<PipelineEntry> entries) { Set<String> packages = new HashSet<>(); - for (ListEntry entry : entries) { + for (PipelineEntry entry : entries) { NotificationEntry notificationEntry = entry.getRepresentativeEntry(); if (notificationEntry == null) { Log.wtf(TAG, "notification entry " + entry.getKey() @@ -302,9 +303,9 @@ public class PreparationCoordinator implements Coordinator { return packages; } - private void inflateAllRequiredViews(List<ListEntry> entries) { + private void inflateAllRequiredViews(List<PipelineEntry> entries) { for (int i = 0, size = entries.size(); i < size; i++) { - ListEntry entry = entries.get(i); + PipelineEntry entry = entries.get(i); if (entry instanceof GroupEntry) { GroupEntry groupEntry = (GroupEntry) entry; inflateRequiredGroupViews(groupEntry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 69b069d792e3..26bc5b14d357 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; @@ -97,7 +97,7 @@ public class RankingCoordinator implements Coordinator { private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting", NotificationPriorityBucketKt.BUCKET_ALERTING) { @Override - public boolean isInSection(ListEntry entry) { + public boolean isInSection(PipelineEntry entry) { return mHighPriorityProvider.isHighPriority(entry); } @@ -115,7 +115,7 @@ public class RankingCoordinator implements Coordinator { private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent", NotificationPriorityBucketKt.BUCKET_SILENT) { @Override - public boolean isInSection(ListEntry entry) { + public boolean isInSection(PipelineEntry entry) { return !mHighPriorityProvider.isHighPriority(entry) && !entry.getRepresentativeEntry().isAmbient(); } @@ -128,7 +128,7 @@ public class RankingCoordinator implements Coordinator { @Nullable @Override - public void onEntriesUpdated(@NonNull List<ListEntry> entries) { + public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) { mHasSilentEntries = false; for (int i = 0; i < entries.size(); i++) { if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { @@ -144,7 +144,7 @@ public class RankingCoordinator implements Coordinator { private final NotifSectioner mMinimizedNotifSectioner = new NotifSectioner("Minimized", NotificationPriorityBucketKt.BUCKET_SILENT) { @Override - public boolean isInSection(ListEntry entry) { + public boolean isInSection(PipelineEntry entry) { return !mHighPriorityProvider.isHighPriority(entry) && entry.getRepresentativeEntry().isAmbient(); } @@ -157,7 +157,7 @@ public class RankingCoordinator implements Coordinator { @Nullable @Override - public void onEntriesUpdated(@NonNull List<ListEntry> entries) { + public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) { mHasMinimizedEntries = false; for (int i = 0; i < entries.size(); i++) { if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt index 4a7b7ca51ba2..930e52a8fd52 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.util.ArrayMap import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope @@ -39,7 +39,7 @@ class RowAlertTimeCoordinator @Inject constructor() : Coordinator { pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry) } - private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) { + private fun onBeforeFinalizeFilterListener(entries: List<PipelineEntry>) { latestAlertTimeBySummary.clear() entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry -> val summary = checkNotNull(groupEntry.summary) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt index df8e56eb4102..a987c544bb50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt @@ -20,7 +20,7 @@ import android.content.Context import com.android.systemui.res.R import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.notification.AssistantFeedbackController -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope @@ -63,7 +63,7 @@ internal constructor( pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry) } - private fun onBeforeRenderList(list: List<ListEntry>) { + private fun onBeforeRenderList(list: List<PipelineEntry>) { entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry -> !mSectionStyleProvider.isMinimizedSection(entry.section!!) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt index 90916d1c77d8..db24e7d0604d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt @@ -32,7 +32,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTIO import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.DynamicPrivacyController import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope @@ -155,7 +155,7 @@ constructor( } } - override fun onBeforeRenderList(entries: List<ListEntry>) { + override fun onBeforeRenderList(entries: List<PipelineEntry>) { if ( isKeyguardGoingAway || statusBarStateController.state == StatusBarState.KEYGUARD && @@ -220,13 +220,15 @@ constructor( } } -private fun extractAllRepresentativeEntries(entries: List<ListEntry>): Sequence<NotificationEntry> = +private fun extractAllRepresentativeEntries(entries: List<PipelineEntry>): Sequence<NotificationEntry> = entries.asSequence().flatMap(::extractAllRepresentativeEntries) -private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> = +private fun extractAllRepresentativeEntries( + pipelineEntry: PipelineEntry, +): Sequence<NotificationEntry> = sequence { - listEntry.representativeEntry?.let { yield(it) } - if (listEntry is GroupEntry) { - yieldAll(extractAllRepresentativeEntries(listEntry.children)) + pipelineEntry.representativeEntry?.let { yield(it) } + if (pipelineEntry is GroupEntry) { + yieldAll(extractAllRepresentativeEntries(pipelineEntry.children)) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt index 1c66db783a34..29a8eb0c25d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.service.notification.NotificationListenerService import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener @@ -67,7 +67,7 @@ class ShadeEventCoordinator @Inject internal constructor( mShadeEmptiedCallback = callback } - private fun onBeforeRenderList(entries: List<ListEntry>) { + private fun onBeforeRenderList(entries: List<PipelineEntry>) { if (mEntryRemoved && entries.isEmpty()) { mLogger.logShadeEmptied() // TODO(b/206023518): This was bad. Do not copy this. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 1cb2366a16fe..53a73f4ced63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.app.tracing.traceSection import com.android.server.notification.Flags.screenshareNotificationHiding import com.android.systemui.Flags.screenshareNotificationHidingBugFix -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl @@ -50,14 +50,14 @@ internal constructor( groupExpansionManagerImpl.attach(pipeline) } - private fun onAfterRenderList(entries: List<ListEntry>) = + private fun onAfterRenderList(entries: List<PipelineEntry>) = traceSection("StackCoordinator.onAfterRenderList") { val notifStats = calculateNotifStats(entries) activeNotificationsInteractor.setNotifStats(notifStats) renderListInteractor.setRenderedList(entries) } - private fun calculateNotifStats(entries: List<ListEntry>): NotifStats { + private fun calculateNotifStats(entries: List<PipelineEntry>): NotifStats { var hasNonClearableAlertingNotifs = false var hasClearableAlertingNotifs = false var hasNonClearableSilentNotifs = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 49d5029bbc70..3e5655a9e925 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -39,7 +39,7 @@ import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; @@ -253,21 +253,20 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { return false; } - final GroupEntry parent = entry.getParent(); + final PipelineEntry parent = entry.getParent(); if (parent == null) { return false; } - return isHeadsUpGroup(parent); } - private boolean isHeadsUpGroup(GroupEntry groupEntry) { - if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) { + private boolean isHeadsUpGroup(PipelineEntry pipelineEntry) { + if (!(pipelineEntry instanceof GroupEntry groupEntry)) { return false; } - if (groupEntry == null) { + if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) { return false; } @@ -370,7 +369,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { } @Override - public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) { + public boolean isEntryReorderingAllowed(@NonNull PipelineEntry entry) { if (StabilizeHeadsUpGroup.isEnabled()) { if (isEveryChangeAllowed()) { return true; @@ -403,7 +402,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { private final OnBeforeRenderListListener mOnBeforeRenderListListener = new OnBeforeRenderListListener() { @Override - public void onBeforeRenderList(List<ListEntry> entries) { + public void onBeforeRenderList(List<PipelineEntry> entries) { if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt index 465bc288cbc1..adcc3ec02f0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt @@ -124,7 +124,7 @@ constructor( val parent = entry.parent ?: error("Entry must have a parent to determine if minimized") val isMinimizedSection = sectionStyleProvider.isMinimizedSection(section) val isTopLevelEntry = parent == GroupEntry.ROOT_ENTRY - val isGroupSummary = parent.summary == entry + val isGroupSummary = (parent as? GroupEntry)?.summary == entry return isMinimizedSection && (isTopLevelEntry || isGroupSummary) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java index ac450c03b850..e3ab81c84bfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; import androidx.annotation.NonNull; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.List; @@ -31,5 +31,5 @@ public interface OnAfterRenderListListener { * @param entries The current list of top-level entries. Note that this is a live view into the * current list and will change whenever the pipeline is rerun. */ - void onAfterRenderList(@NonNull List<ListEntry> entries); + void onAfterRenderList(@NonNull List<PipelineEntry> entries); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeFinalizeFilterListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeFinalizeFilterListener.java index 086661ea219b..3c4f4225388c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeFinalizeFilterListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeFinalizeFilterListener.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.List; @@ -30,5 +30,5 @@ public interface OnBeforeFinalizeFilterListener { * @param entries The current list of top-level entries. Note that this is a live view into the * current list and will change whenever the pipeline is rerun. */ - void onBeforeFinalizeFilter(List<ListEntry> entries); + void onBeforeFinalizeFilter(List<PipelineEntry> entries); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java index 44a27a4b546a..6ceb7d5653f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.List; @@ -30,5 +30,5 @@ public interface OnBeforeRenderListListener { * @param entries The current list of top-level entries. Note that this is a live view into the * current list and will change whenever the pipeline is rerun. */ - void onBeforeRenderList(List<ListEntry> entries); + void onBeforeRenderList(List<PipelineEntry> entries); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java index 56cfe5cb3716..858cec1e80b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.List; @@ -30,5 +30,5 @@ public interface OnBeforeSortListener { * @param entries The current list of top-level entries. Note that this is a live view into the * current list and will change whenever the pipeline is rerun. */ - void onBeforeSort(List<ListEntry> entries); + void onBeforeSort(List<PipelineEntry> entries); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java index 0dc4df0da066..eb525c7c4505 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; @@ -35,5 +35,5 @@ public interface OnBeforeTransformGroupsListener { * a live view into the current notif list and will change as the list moves through * the pipeline. */ - void onBeforeTransformGroups(List<ListEntry> list); + void onBeforeTransformGroups(List<PipelineEntry> list); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt index d8f75f61c05a..0d8a64abd13d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.notification.collection.listbuilder -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry object ShadeListBuilderHelper { - fun getSectionSubLists(entries: List<ListEntry>): Iterable<List<ListEntry>> = + fun getSectionSubLists(entries: List<PipelineEntry>): Iterable<List<PipelineEntry>> = getContiguousSubLists(entries, minLength = 1) { it.sectionIndex } inline fun <T : Any, K : Any> getContiguousSubLists( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index a8409d0c6fa0..8a9548f0b9d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -23,7 +23,7 @@ import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.StateName import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.getStateName @@ -152,9 +152,9 @@ class ShadeListBuilderLogger @Inject constructor( fun logEntryAttachStateChanged( buildId: Int, - entry: ListEntry, - prevParent: GroupEntry?, - newParent: GroupEntry? + entry: PipelineEntry, + prevParent: PipelineEntry?, + newParent: PipelineEntry? ) { buffer.log(TAG, INFO, { long1 = buildId.toLong() @@ -177,7 +177,7 @@ class ShadeListBuilderLogger @Inject constructor( }) } - fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) { + fun logParentChanged(buildId: Int, prevParent: PipelineEntry?, newParent: PipelineEntry?) { buffer.log(TAG, INFO, { long1 = buildId.toLong() str1 = prevParent?.logKey @@ -195,8 +195,8 @@ class ShadeListBuilderLogger @Inject constructor( fun logParentChangeSuppressedStarted( buildId: Int, - suppressedParent: GroupEntry?, - keepingParent: GroupEntry? + suppressedParent: PipelineEntry?, + keepingParent: PipelineEntry? ) { buffer.log(TAG, INFO, { long1 = buildId.toLong() @@ -209,8 +209,8 @@ class ShadeListBuilderLogger @Inject constructor( fun logParentChangeSuppressedStopped( buildId: Int, - previouslySuppressedParent: GroupEntry?, - previouslyKeptParent: GroupEntry? + previouslySuppressedParent: PipelineEntry?, + previouslyKeptParent: PipelineEntry? ) { buffer.log(TAG, INFO, { long1 = buildId.toLong() @@ -224,7 +224,7 @@ class ShadeListBuilderLogger @Inject constructor( fun logGroupPruningSuppressed( buildId: Int, - keepingParent: GroupEntry? + keepingParent: PipelineEntry? ) { buffer.log(TAG, INFO, { long1 = buildId.toLong() @@ -310,7 +310,7 @@ class ShadeListBuilderLogger @Inject constructor( val logRankInFinalList = Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled() - fun logFinalList(entries: List<ListEntry>) { + fun logFinalList(entries: List<PipelineEntry>) { if (entries.isEmpty()) { buffer.log(TAG, DEBUG, {}, { "(empty list)" }) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java index f7bbd281ec51..f7c74c217cdf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.plugg import androidx.annotation.NonNull; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.Comparator; @@ -29,7 +29,7 @@ import java.util.List; */ public abstract class NotifComparator extends Pluggable<NotifComparator> - implements Comparator<ListEntry> { + implements Comparator<PipelineEntry> { protected NotifComparator(String name) { super(name); @@ -41,5 +41,5 @@ public abstract class NotifComparator * @return a negative integer, zero, or a positive integer as the first argument is less than * equal to, or greater than the second (same as standard Comparator<> interface). */ - public abstract int compare(@NonNull ListEntry o1, @NonNull ListEntry o2); + public abstract int compare(@NonNull PipelineEntry o1, @NonNull PipelineEntry o2); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java index 8c52c53ea6b2..4a856a55f185 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.plugg import android.annotation.Nullable; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import com.android.systemui.statusbar.notification.collection.render.NodeController; import com.android.systemui.statusbar.notification.collection.render.NodeSpec; @@ -52,11 +52,11 @@ public abstract class NotifSectioner extends Pluggable<NotifSectioner> { * However, this doesn't necessarily mean that your section will get called on each top-level * notification. The first section to return true determines the section of the notification. */ - public abstract boolean isInSection(ListEntry entry); + public abstract boolean isInSection(PipelineEntry entry); /** * Returns an optional {@link NotifComparator} to sort entries only in this section. - * {@link ListEntry} instances passed to this comparator are guaranteed to have this section, + * {@link PipelineEntry} instances passed to this comparator are guaranteed to have this section, * and this ordering will take precedence over any global comparators supplied to {@link * com.android.systemui.statusbar.notification.collection.NotifPipeline#setComparators(List)}. * @@ -80,5 +80,5 @@ public abstract class NotifSectioner extends Pluggable<NotifSectioner> { * Notify of children of this section being updated * @param entries of this section that are borrowed (must clone to store) */ - public void onEntriesUpdated(List<ListEntry> entries) {} + public void onEntriesUpdated(List<PipelineEntry> entries) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt index 58bbca822164..e1e2bcff3e31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry /** @@ -75,7 +75,7 @@ abstract class NotifStabilityManager protected constructor(name: String) : * being suppressed. However, if an order change is suppressed, that will be reported to ths * implementation by calling [onEntryReorderSuppressed] after ordering is complete. */ - abstract fun isEntryReorderingAllowed(entry: ListEntry): Boolean + abstract fun isEntryReorderingAllowed(entry: PipelineEntry): Boolean /** * Called by the pipeline to determine if every call to the other stability methods would @@ -100,7 +100,7 @@ object DefaultNotifStabilityManager : NotifStabilityManager("DefaultNotifStabili override fun isGroupChangeAllowed(entry: NotificationEntry): Boolean = true override fun isGroupPruneAllowed(entry: GroupEntry): Boolean = true override fun isSectionChangeAllowed(entry: NotificationEntry): Boolean = true - override fun isEntryReorderingAllowed(entry: ListEntry): Boolean = true + override fun isEntryReorderingAllowed(entry: PipelineEntry): Boolean = true override fun isEveryChangeAllowed(): Boolean = true override fun onEntryReorderSuppressed() {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index 014ac549591e..ae2c70a284e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -27,6 +27,7 @@ import com.android.systemui.log.core.LogLevel.ERROR import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.log.core.LogLevel.WTF +import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal @@ -421,6 +422,14 @@ class NotifCollectionLogger @Inject constructor( }) } + private fun getParentLogKey(childEntry: NotificationEntry): String { + return if (childEntry.parent is GroupEntry) { + (childEntry.parent as? GroupEntry)?.summary?.logKey ?: "(null)" + } else { + "(null)" + } + } + fun logDismissAlreadyParentDismissedNotif( childEntry: NotificationEntry, childIndex: Int, @@ -430,7 +439,7 @@ class NotifCollectionLogger @Inject constructor( str1 = childEntry.logKey int1 = childIndex int2 = childCount - str2 = childEntry.parent?.summary?.logKey ?: "(null)" + str2 = getParentLogKey(childEntry) }, { "DISMISS Already Parent-Dismissed $str1 ($int1/$int2) with summary $str2" }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java index 2e3ab926ad57..051bd3a99549 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -23,7 +23,7 @@ import android.app.NotificationManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; @@ -53,7 +53,7 @@ public class HighPriorityProvider { } /** - * @return true if the ListEntry is high priority, else false + * @return true if the PipelineEntry is high priority, else false * * A NotificationEntry is considered high priority if it: * - has importance greater than or equal to IMPORTANCE_DEFAULT @@ -67,12 +67,12 @@ public class HighPriorityProvider { * A GroupEntry is considered high priority if its representativeEntry (summary) or any of its * children are high priority. */ - public boolean isHighPriority(@Nullable ListEntry entry) { + public boolean isHighPriority(@Nullable PipelineEntry entry) { return isHighPriority(entry, /* allowImplicit = */ true); } /** - * @return true if the ListEntry is explicitly high priority, else false + * @return true if the PipelineEntry is explicitly high priority, else false * * A NotificationEntry is considered explicitly high priority if has importance greater than or * equal to IMPORTANCE_DEFAULT. @@ -80,11 +80,11 @@ public class HighPriorityProvider { * A GroupEntry is considered explicitly high priority if its representativeEntry (summary) or * any of its children are explicitly high priority. */ - public boolean isExplicitlyHighPriority(@Nullable ListEntry entry) { + public boolean isExplicitlyHighPriority(@Nullable PipelineEntry entry) { return isHighPriority(entry, /* allowImplicit= */ false); } - private boolean isHighPriority(@Nullable ListEntry entry, boolean allowImplicit) { + private boolean isHighPriority(@Nullable PipelineEntry entry, boolean allowImplicit) { if (entry == null) { return false; } @@ -100,9 +100,9 @@ public class HighPriorityProvider { } /** - * @return true if the ListEntry is high priority conversation, else false + * @return true if the PipelineEntry is high priority conversation, else false */ - public boolean isHighPriorityConversation(@NonNull ListEntry entry) { + public boolean isHighPriorityConversation(@NonNull PipelineEntry entry) { final NotificationEntry notifEntry = entry.getRepresentativeEntry(); if (notifEntry == null) { return false; @@ -119,7 +119,7 @@ public class HighPriorityProvider { return isNotificationEntryWithAtLeastOneImportantChild(entry); } - private boolean isNotificationEntryWithAtLeastOneImportantChild(@NonNull ListEntry entry) { + private boolean isNotificationEntryWithAtLeastOneImportantChild(@NonNull PipelineEntry entry) { if (!(entry instanceof GroupEntry)) { return false; } @@ -134,7 +134,7 @@ public class HighPriorityProvider { * Returns whether the given ListEntry has a high priority child or is in a group with a child * that's high priority */ - private boolean hasHighPriorityChild(ListEntry entry, boolean allowImplicit) { + private boolean hasHighPriorityChild(PipelineEntry entry, boolean allowImplicit) { if (NotificationBundleUi.isEnabled()) { GroupEntry representativeGroupEntry = null; if (entry instanceof GroupEntry) { @@ -142,9 +142,10 @@ public class HighPriorityProvider { } else if (entry instanceof NotificationEntry){ final NotificationEntry notificationEntry = entry.getRepresentativeEntry(); if (notificationEntry.getParent() != null - && notificationEntry.getParent().getSummary() != null - && notificationEntry.getParent().getSummary() == notificationEntry) { - representativeGroupEntry = notificationEntry.getParent(); + && notificationEntry.getParent() instanceof GroupEntry parent + && parent.getSummary() != null + && parent.getSummary() == notificationEntry) { + representativeGroupEntry = parent; } } return representativeGroupEntry != null && diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt index ea9f29521459..f418bb639f49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.provider import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner @@ -53,7 +53,7 @@ class SectionStyleProvider @Inject constructor( * Determine if the given entry is minimized. */ @JvmOverloads - fun isMinimized(entry: ListEntry, ifNotInSection: Boolean = true): Boolean { + fun isMinimized(entry: PipelineEntry, ifNotInSection: Boolean = true): Boolean { val section = entry.section ?: return ifNotInSection return isMinimizedSection(section) } @@ -77,7 +77,7 @@ class SectionStyleProvider @Inject constructor( * Determine if the given entry is silent. */ @JvmOverloads - fun isSilent(entry: ListEntry, ifNotInSection: Boolean = true): Boolean { + fun isSilent(entry: PipelineEntry, ifNotInSection: Boolean = true): Boolean { val section = entry.section ?: return ifNotInSection if (SortBySectionTimeFlag.isEnabled) { if (entry.section?.bucket == BUCKET_PEOPLE) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java index 16b98e20498a..b179a694dad7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java @@ -25,7 +25,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; @@ -81,7 +81,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl } final Set<NotificationEntry> renderingSummaries = new HashSet<>(); - for (ListEntry entry : entries) { + for (PipelineEntry entry : entries) { if (entry instanceof GroupEntry) { renderingSummaries.add(entry.getRepresentativeEntry()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java index 69267e5d9e55..3edbfafd7d33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import java.util.List; @@ -78,5 +79,5 @@ public interface GroupMembershipManager { * @return list of the children */ @Nullable - List<NotificationEntry> getChildren(@NonNull ListEntry summary); + List<NotificationEntry> getChildren(@NonNull PipelineEntry summary); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java index 80a9f8adf8f3..a1a23e3a0b44 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java @@ -24,7 +24,7 @@ import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; @@ -48,8 +48,13 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager { // The entry is not attached, so it doesn't count. return false; } + PipelineEntry pipelineEntry = entry.getParent(); + if (!(pipelineEntry instanceof GroupEntry groupEntry)) { + return false; + } + // If entry is a summary, its parent is a GroupEntry with summary = entry. - return entry.getParent().getSummary() == entry; + return groupEntry.getSummary() == entry; } @Override @@ -65,7 +70,10 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager { if (isTopLevelEntry(entry) || entry.getParent() == null) { return null; } - return entry.getParent().getSummary(); + if (entry.getParent() instanceof GroupEntry parent) { + return parent.getSummary(); + } + return null; } @Nullable @@ -91,8 +99,9 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager { @Nullable @Override - public List<NotificationEntry> getChildren(@NonNull ListEntry entry) { + public List<NotificationEntry> getChildren(@NonNull PipelineEntry entry) { NotificationBundleUi.assertInLegacyMode(); + if (entry instanceof GroupEntry) { return ((GroupEntry) entry).getChildren(); } @@ -100,8 +109,7 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager { NotificationEntry representativeEntry = entry.getRepresentativeEntry(); if (representativeEntry != null && isGroupSummary(representativeEntry)) { // maybe we were actually passed the summary - GroupEntry parent = representativeEntry.getParent(); - if (parent != null) { + if (representativeEntry.getParent() instanceof GroupEntry parent) { return parent.getChildren(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt index 9fc4e4036b31..e17319484cce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt @@ -18,12 +18,12 @@ package com.android.systemui.statusbar.notification.collection.render import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider import com.android.systemui.util.Compile import com.android.app.tracing.traceSection +import com.android.systemui.statusbar.notification.collection.PipelineEntry /** * Converts a notif list (the output of the ShadeListBuilder) into a NodeSpec, an abstract @@ -45,7 +45,7 @@ class NodeSpecBuilder( fun buildNodeSpec( rootController: NodeController, - notifList: List<ListEntry> + notifList: List<PipelineEntry> ): NodeSpec = traceSection("NodeSpecBuilder.buildNodeSpec") { val root = NodeSpecImpl(null, rootController) @@ -100,7 +100,7 @@ class NodeSpecBuilder( return@traceSection root } - private fun buildNotifNode(parent: NodeSpec, entry: ListEntry): NodeSpec = when (entry) { + private fun buildNotifNode(parent: NodeSpec, entry: PipelineEntry): NodeSpec = when (entry) { is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireNodeController(entry)) is GroupEntry -> NodeSpecImpl(parent, viewBarn.requireNodeController(checkNotNull(entry.summary))) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt index fd91d5a2c082..56662f105e4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt @@ -18,18 +18,18 @@ package com.android.systemui.statusbar.notification.collection.render import android.view.textclassifier.Log import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject /** - * The ViewBarn is just a map from [ListEntry] to an instance of a [NodeController]. + * The ViewBarn is just a map from [PipelineEntry] to an instance of a [NodeController]. */ @SysUISingleton class NotifViewBarn @Inject constructor() { private val rowMap = mutableMapOf<String, NotifViewController>() - fun requireNodeController(entry: ListEntry): NodeController { + fun requireNodeController(entry: PipelineEntry): NodeController { if (DEBUG) { Log.d(TAG, "requireNodeController: ${entry.key}") } @@ -50,14 +50,14 @@ class NotifViewBarn @Inject constructor() { return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}") } - fun registerViewForEntry(entry: ListEntry, controller: NotifViewController) { + fun registerViewForEntry(entry: PipelineEntry, controller: NotifViewController) { if (DEBUG) { Log.d(TAG, "registerViewForEntry: ${entry.key}") } rowMap[entry.key] = controller } - fun removeViewForEntry(entry: ListEntry) { + fun removeViewForEntry(entry: PipelineEntry) { if (DEBUG) { Log.d(TAG, "removeViewForEntry: ${entry.key}") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt index 8284022c7270..396d47e7096c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.render import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry /** @@ -34,7 +34,7 @@ interface NotifViewRenderer { * also ensure that future calls to [getStackController], [getGroupController], and * [getRowController] will provide valid results. */ - fun onRenderList(notifList: List<ListEntry>) + fun onRenderList(notifList: List<PipelineEntry>) /** * Provides an interface for the pipeline to update individual groups. This will be called at diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt index 21e68376031c..93e844de79d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render import com.android.app.tracing.traceSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.PipelineDumpable import com.android.systemui.statusbar.notification.collection.PipelineDumper @@ -46,7 +46,7 @@ class RenderStageManager @Inject constructor() : PipelineDumpable { listBuilder.setOnRenderListListener(::onRenderList) } - private fun onRenderList(notifList: List<ListEntry>) { + private fun onRenderList(notifList: List<PipelineEntry>) { traceSection("RenderStageManager.onRenderList") { val viewRenderer = viewRenderer ?: return viewRenderer.onRenderList(notifList) @@ -85,7 +85,7 @@ class RenderStageManager @Inject constructor() : PipelineDumpable { dump("onAfterRenderEntryListeners", onAfterRenderEntryListeners) } - private fun dispatchOnAfterRenderList(entries: List<ListEntry>) { + private fun dispatchOnAfterRenderList(entries: List<PipelineEntry>) { traceSection("RenderStageManager.dispatchOnAfterRenderList") { onAfterRenderListListeners.forEach { listener -> listener.onAfterRenderList(entries) } } @@ -93,7 +93,7 @@ class RenderStageManager @Inject constructor() : PipelineDumpable { private fun dispatchOnAfterRenderGroups( viewRenderer: NotifViewRenderer, - entries: List<ListEntry>, + entries: List<PipelineEntry>, ) { traceSection("RenderStageManager.dispatchOnAfterRenderGroups") { if (onAfterRenderGroupListeners.isEmpty()) { @@ -110,7 +110,7 @@ class RenderStageManager @Inject constructor() : PipelineDumpable { private fun dispatchOnAfterRenderEntries( viewRenderer: NotifViewRenderer, - entries: List<ListEntry>, + entries: List<PipelineEntry>, ) { traceSection("RenderStageManager.dispatchOnAfterRenderEntries") { if (onAfterRenderEntryListeners.isEmpty()) { @@ -129,7 +129,7 @@ class RenderStageManager @Inject constructor() : PipelineDumpable { * Performs a forward, depth-first traversal of the list where the group's summary immediately * precedes the group's children. */ - private inline fun List<ListEntry>.forEachNotificationEntry( + private inline fun List<PipelineEntry>.forEachNotificationEntry( action: (NotificationEntry) -> Unit ) { forEach { entry -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt index 72316bf14c9a..9532d6d89cf7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt @@ -22,7 +22,7 @@ import com.android.app.tracing.traceSection import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.PipelineDumpable import com.android.systemui.statusbar.notification.collection.PipelineDumper @@ -76,7 +76,7 @@ constructor( private val viewRenderer = object : NotifViewRenderer { - override fun onRenderList(notifList: List<ListEntry>) { + override fun onRenderList(notifList: List<PipelineEntry>) { traceSection("ShadeViewManager.onRenderList") { viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt index bde3c4d8c632..adc049e7cdf1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt @@ -29,7 +29,7 @@ import com.android.app.tracing.traceSection import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository @@ -57,11 +57,11 @@ constructor( /** * Sets the current list of rendered notification entries as displayed in the notification list. */ - fun setRenderedList(entries: List<ListEntry>) { + fun setRenderedList(entries: List<PipelineEntry>) { traceSection("RenderNotificationListInteractor.setRenderedList") { repository.activeNotifications.update { existingModels -> buildActiveNotificationsStore(existingModels, sectionStyleProvider, context) { - entries.forEach(::addListEntry) + entries.forEach(::addPipelineEntry) setRankingsMap(entries) } } @@ -89,11 +89,11 @@ private class ActiveNotificationsStoreBuilder( fun build(): ActiveNotificationsStore = builder.build() /** - * Convert a [ListEntry] into [ActiveNotificationEntryModel]s, and add them to the + * Convert a [PipelineEntry] into [ActiveNotificationEntryModel]s, and add them to the * [ActiveNotificationsStore]. Special care is taken to avoid re-allocating models if the result * would be identical to an existing model (at the expense of additional computations). */ - fun addListEntry(entry: ListEntry) { + fun addPipelineEntry(entry: PipelineEntry) { when (entry) { is GroupEntry -> { entry.summary?.let { summary -> @@ -116,11 +116,11 @@ private class ActiveNotificationsStoreBuilder( } } - fun setRankingsMap(entries: List<ListEntry>) { + fun setRankingsMap(entries: List<PipelineEntry>) { builder.setRankingsMap(flatMapToRankingsMap(entries)) } - fun flatMapToRankingsMap(entries: List<ListEntry>): Map<String, Int> { + fun flatMapToRankingsMap(entries: List<PipelineEntry>): Map<String, Int> { val result = ArrayMap<String, Int>() for (entry in entries) { if (entry is NotificationEntry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index c7548aae7131..5096894911c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -19,7 +19,7 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController -import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.policy.KeyguardStateController @@ -198,7 +198,7 @@ constructor( else -> false } - private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = + private fun shouldHideIfEntrySilent(entry: PipelineEntry): Boolean = when { // Show if explicitly high priority (not hidden) highPriorityProvider.isExplicitlyHighPriority(entry) -> false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt index f755dbb48e1d..002230e4e516 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt @@ -20,6 +20,7 @@ import android.annotation.IntDef import android.service.notification.NotificationListenerService.Ranking import android.service.notification.StatusBarNotification import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType @@ -117,8 +118,8 @@ class PeopleNotificationIdentifierImpl @Inject constructor( if (!entry.sbn.notification.isGroupSummary) { return TYPE_NON_PERSON; } - - return getPeopleTypeForChildList(entry.parent?.children) + val parent = entry.parent as? GroupEntry ?: return TYPE_NON_PERSON + return getPeopleTypeForChildList(parent.children) } else { if (!groupManager.isGroupSummary(entry)) { return TYPE_NON_PERSON diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt index 9aa5a2e32ada..7959e99f3189 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.promoted import android.app.Flags +import android.app.Flags.notificationsRedesignTemplates import android.app.Notification import android.graphics.PorterDuff import android.view.LayoutInflater @@ -166,7 +167,10 @@ private class AODPromotedNotificationViewUpdater(root: View) { private val closeButton: View? = root.findViewById(R.id.close_button) private val conversationIconBadge: View? = root.findViewById(R.id.conversation_icon_badge) private val conversationIcon: CachingIconView? = root.findViewById(R.id.conversation_icon) - private val conversationText: TextView? = root.findViewById(R.id.conversation_text) + private val conversationText: TextView? = + root.findViewById( + if (notificationsRedesignTemplates()) R.id.title else R.id.conversation_text + ) private val expandButton: NotificationExpandButton? = root.findViewById(R.id.expand_button) private val headerText: TextView? = root.findViewById(R.id.header_text) private val headerTextDivider: View? = root.findViewById(R.id.header_text_divider) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 64cd6174a585..d2800d7c1b71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -46,6 +46,7 @@ import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; @@ -117,7 +118,6 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.logging.NotificationCounters; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper; @@ -169,6 +169,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); private static final SourceType BASE_VALUE = SourceType.from("BaseValue"); private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)"); + private static final long INITIALIZATION_DELAY = 400; // We don't correctly track dark mode until the content views are inflated, so always update // the background on first content update just in case it happens to be during a theme change. @@ -267,7 +268,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private NotificationContentView[] mLayouts; - private int mNotificationColor; private ExpandableNotificationRowLogger mLogger; private String mLoggingKey; private NotificationGuts mGuts; @@ -352,6 +352,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ private boolean mSaveSpaceOnLockscreen; + // indicates when this view was first attached to a window + // this value will reset when the view is completely removed from the shade (ie: filtered out) + private long initializationTime = -1; + /** * It is added for unit testing purpose. * Please do not use it for other purposes. @@ -625,26 +629,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Marks a content view as freeable, setting it so that future inflations do not reinflate - * and ensuring that the view is freed when it is safe to remove. - * - * @param inflationFlag flag corresponding to the content view to be freed - * @deprecated By default, {@link NotificationRowContentBinder#unbindContent} will tell the - * view hierarchy to only free when the view is safe to remove so this method is no longer - * needed. Will remove when all uses are gone. - */ - @Deprecated - public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { - RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); - params.markContentViewsFreeable(inflationFlag); - mRowContentBindStage.requestRebind(mEntry, null /* callback */); - } - - /** * Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif * or is in an allowList). */ public boolean getIsNonblockable() { + NotificationBundleUi.assertInLegacyMode(); if (mEntry == null) { return true; } @@ -666,9 +655,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.onNotificationUpdated(mEntry); } mShowingPublicInitialized = false; - updateNotificationColor(); if (mMenuRow != null) { - mMenuRow.onNotificationUpdated(mEntry.getSbn()); + mMenuRow.onNotificationUpdated(); mMenuRow.setAppName(mAppName); } if (mIsSummaryWithChildren) { @@ -727,15 +715,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Called when the notification's ranking was changed (but nothing else changed). - */ - public void onNotificationRankingUpdated() { - if (mMenuRow != null) { - mMenuRow.onNotificationUpdated(mEntry.getSbn()); - } - } - - /** * Call when bubble state has changed and the button on the notification should be updated. */ public void updateBubbleButton() { @@ -746,7 +725,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @VisibleForTesting void updateShelfIconColor() { - StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon(); + StatusBarIconView expandedIcon = getShelfIcon(); boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L)); boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon, ContrastColorUtil.getInstance(mContext)); @@ -767,8 +746,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (color != Notification.COLOR_INVALID) { return color; } else { - return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(), - getBackgroundColorWithoutTint()); + if (NotificationBundleUi.isEnabled()) { + return mEntryAdapter.getContrastedColor(mContext, mIsMinimized && !isExpanded(), + getBackgroundColorWithoutTint()); + } else { + return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(), + getBackgroundColorWithoutTint()); + } } } @@ -870,15 +854,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView boolean customView = contractedView != null && contractedView.getId() != com.android.internal.R.id.status_bar_latest_event_content; - boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; - boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P; - boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S; + int targetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT; + if (NotificationBundleUi.isEnabled()) { + targetSdk = mEntryAdapter.getTargetSdk(); + } else { + targetSdk = mEntry.targetSdk; + } + + boolean beforeN = targetSdk < Build.VERSION_CODES.N; + boolean beforeP = targetSdk < Build.VERSION_CODES.P; + boolean beforeS = targetSdk < Build.VERSION_CODES.S; int smallHeight; boolean isCallLayout = contractedView instanceof CallLayout; boolean isMessagingLayout = contractedView instanceof MessagingLayout || contractedView instanceof ConversationLayout; + String summarization = null; + if (NotificationBundleUi.isEnabled()) { + summarization = mEntryAdapter.getSummarization(); + } else { + summarization = mEntry.getRanking().getSummarization(); + } + if (customView && beforeS && !mIsSummaryWithChildren) { if (beforeN) { smallHeight = mMaxSmallHeightBeforeN; @@ -891,7 +889,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView smallHeight = maxExpandedHeight; } else if (NmSummarizationUiFlag.isEnabled() && isMessagingLayout - && !TextUtils.isEmpty(mEntry.getRanking().getSummarization())) { + && !TextUtils.isEmpty(summarization)) { smallHeight = mMaxSmallHeightWithSummarization; } else { smallHeight = mMaxSmallHeight; @@ -1524,7 +1522,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public boolean hasFinishedInitialization() { - return getEntry().hasFinishedInitialization(); + if (NotificationBundleUi.isEnabled()) { + return initializationTime != -1 + && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY; + } else { + return getEntry().hasFinishedInitialization(); + } + } + + public void resetInitializationTime() { + initializationTime = -1; + } + + public void setInitializationTime(long time) { + if (initializationTime == -1) { + initializationTime = time; + } } /** @@ -1539,7 +1552,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return null; } if (mMenuRow.getMenuView() == null) { - mMenuRow.createMenu(this, mEntry.getSbn()); + mMenuRow.createMenu(this); mMenuRow.setAppName(mAppName); FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); @@ -1581,7 +1594,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (oldMenu != null) { int menuIndex = indexOfChild(oldMenu); removeView(oldMenu); - mMenuRow.createMenu(ExpandableNotificationRow.this, mEntry.getSbn()); + mMenuRow.createMenu(ExpandableNotificationRow.this); mMenuRow.setAppName(mAppName); addView(mMenuRow.getMenuView(), menuIndex); } @@ -1589,12 +1602,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.reinflate(); l.reInflateViews(); } - mEntry.getSbn().clearPackageContext(); + if (NotificationBundleUi.isEnabled()) { + mEntryAdapter.prepareForInflation(); + } else { + mEntry.getSbn().clearPackageContext(); + } // TODO: Move content inflation logic out of this call RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); params.setNeedsReinflation(true); - var rebindEndCallback = mRebindingTracker.trackRebinding(mEntry.getKey()); + var rebindEndCallback = mRebindingTracker.trackRebinding(NotificationBundleUi.isEnabled() + ? mEntryAdapter.getKey() : mEntry.getKey()); mRowContentBindStage.requestRebind(mEntry, (e) -> rebindEndCallback.onFinished()); Trace.endSection(); } @@ -1658,20 +1676,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout.setSingleLineWidthIndention(indention); } - public int getNotificationColor() { - return mNotificationColor; - } - - public void updateNotificationColor() { - Configuration currentConfig = getResources().getConfiguration(); - boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - - mNotificationColor = ContrastColorUtil.resolveContrastColor(mContext, - mEntry.getSbn().getNotification().color, - getBackgroundColorWithoutTint(), nightMode); - } - public HybridNotificationView getSingleLineView() { return mPrivateLayout.getSingleLineView(); } @@ -2178,7 +2182,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView R.dimen.notification_min_height); } mMaxSmallHeightWithSummarization = NotificationUtils.getFontScaledHeight(mContext, - com.android.internal.R.dimen.notification_collapsed_height_with_summarization); + com.android.internal.R.dimen.notification_min_height); mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_height); mMaxExpandedHeightForPromotedOngoing = NotificationUtils.getFontScaledHeight(mContext, @@ -2260,6 +2264,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mEntry = entry; } + @VisibleForTesting + protected void setEntryAdapter(EntryAdapter entry) { + mEntryAdapter = entry; + } + private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false); private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) { @@ -2497,7 +2506,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mTranslateableViews.get(i).setTranslationX(0); } invalidateOutline(); - getEntry().getIcons().getShelfIcon().setScrollX(0); + getShelfIcon().setScrollX(0); } if (mMenuRow != null) { @@ -2616,7 +2625,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // In order to keep the shelf in sync with this swiping, we're simply translating // it's icon by the same amount. The translation is already being used for the normal // positioning, so we can use the scrollX instead. - getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX); + getShelfIcon().setScrollX((int) -translationX); } if (mMenuRow != null && mMenuRow.getMenuView() != null) { @@ -2843,7 +2852,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public @NonNull StatusBarIconView getShelfIcon() { - return getEntry().getIcons().getShelfIcon(); + if (NotificationBundleUi.isEnabled()) { + return getEntryAdapter().getIcons().getShelfIcon(); + } else { + return mEntry.getIcons().getShelfIcon(); + } } @Override @@ -3072,8 +3085,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * except for legacy use cases. */ public boolean canShowHeadsUp() { + boolean canEntryHun = NotificationBundleUi.isEnabled() + ? mEntryAdapter.canPeek() + : mEntry.isStickyAndNotDemoted(); if (mOnKeyguard && !isDozing() && !isBypassEnabled() && - (!mEntry.isStickyAndNotDemoted() + (!canEntryHun || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) { return false; } @@ -3112,7 +3128,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } if (!mIsSummaryWithChildren && wasSummary) { // Reset the 'when' once the row stops being a summary - mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen()); + if (NotificationBundleUi.isEnabled()) { + mPublicLayout.setNotificationWhen(mEntryAdapter.getWhen()); + } else { + mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen()); + } } getShowingLayout().updateBackgroundColor(false /* animate */); mPrivateLayout.updateExpandButtons(isExpandable()); @@ -3381,7 +3401,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ @Override public boolean canExpandableViewBeDismissed() { - if (areGutsExposed() || !mEntry.hasFinishedInitialization()) { + if (areGutsExposed() || !hasFinishedInitialization()) { return false; } return canViewBeDismissed(); @@ -3405,7 +3425,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * clearability see {@link NotificationEntry#isClearable()}. */ public boolean canViewBeCleared() { - return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + if (NotificationBundleUi.isEnabled()) { + return mEntryAdapter.isClearable() + && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + } else { + return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + } } private boolean shouldShowPublic() { @@ -4082,6 +4107,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public boolean isMediaRow() { + NotificationBundleUi.assertInLegacyMode(); return mEntry.getSbn().getNotification().isMediaNotification(); } @@ -4204,7 +4230,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void dump(PrintWriter pwOriginal, String[] args) { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); // Skip super call; dump viewState ourselves - pw.println("Notification: " + mEntry.getKey()); + if (NotificationBundleUi.isEnabled()) { + pw.println("Notification: " + mEntryAdapter.getKey()); + } else { + pw.println("Notification: " + mEntry.getKey()); + } DumpUtilsKt.withIncreasedIndent(pw, () -> { pw.println(this); pw.print("visibility: " + getVisibility()); @@ -4235,6 +4265,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView pw.print(", isPinned: " + isPinned()); pw.print(", expandedWhenPinned: " + mExpandedWhenPinned); pw.print(", isMinimized: " + mIsMinimized); + pw.print(", isAboveShelf: " + isAboveShelf()); pw.println(); if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index b43a387a5edb..07711b6e0eb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -374,7 +374,11 @@ public class ExpandableNotificationRowController implements NotifViewController mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { - mView.getEntry().setInitializationTime(mClock.elapsedRealtime()); + if (NotificationBundleUi.isEnabled()) { + mView.setInitializationTime(mClock.elapsedRealtime()); + } else { + mView.getEntry().setInitializationTime(mClock.elapsedRealtime()); + } mPluginManager.addPluginListener(mView, NotificationMenuRowPlugin.class, false /* Allow multiple */); if (!SceneContainerFlag.isEnabled()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6e638f5de209..9a75253295d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -73,6 +73,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.row.icon.AppIconProvider; import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -436,7 +437,9 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta onNasFeedbackClick, mUiEventLogger, mDeviceProvisionedController.isDeviceProvisioned(), - row.getIsNonblockable(), + NotificationBundleUi.isEnabled() + ? !row.getEntry().isBlockable() + : row.getIsNonblockable(), mHighPriorityProvider.isHighPriority(row.getEntry()), mAssistantFeedbackController, mMetricsLogger, @@ -480,7 +483,9 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta row.getEntry(), onSettingsClick, mDeviceProvisionedController.isDeviceProvisioned(), - row.getIsNonblockable()); + NotificationBundleUi.isEnabled() + ? !row.getEntry().isBlockable() + : row.getIsNonblockable()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index b96b224a7d2e..ab382df13d10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -186,7 +186,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } @Override - public void createMenu(ViewGroup parent, StatusBarNotification sbn) { + public void createMenu(ViewGroup parent) { mParent = (ExpandableNotificationRow) parent; createMenuViews(true /* resetState */); } @@ -227,7 +227,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } @Override - public void onNotificationUpdated(StatusBarNotification sbn) { + public void onNotificationUpdated() { if (mMenuContainer == null) { // Menu hasn't been created yet, no need to do anything. return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt index 2d946945597e..e887e25b74a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row.wrapper +import android.app.Flags.notificationsRedesignTemplates import android.content.Context import android.view.View import com.android.internal.widget.CachingIconView @@ -25,17 +26,13 @@ import com.android.systemui.statusbar.notification.NotificationFadeAware import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -/** - * Wraps a notification containing a call template - */ -class NotificationCallTemplateViewWrapper constructor( - ctx: Context, - view: View, - row: ExpandableNotificationRow -) : NotificationTemplateViewWrapper(ctx, view, row) { +/** Wraps a notification containing a call template */ +class NotificationCallTemplateViewWrapper +constructor(ctx: Context, view: View, row: ExpandableNotificationRow) : + NotificationTemplateViewWrapper(ctx, view, row) { private val minHeightWithActions: Int = - NotificationUtils.getFontScaledHeight(ctx, R.dimen.notification_max_height) + NotificationUtils.getFontScaledHeight(ctx, R.dimen.notification_max_height) private val callLayout: CallLayout = view as CallLayout private lateinit var conversationIconContainer: View @@ -48,13 +45,17 @@ class NotificationCallTemplateViewWrapper constructor( private fun resolveViews() { with(callLayout) { conversationIconContainer = - requireViewById(com.android.internal.R.id.conversation_icon_container) + requireViewById(com.android.internal.R.id.conversation_icon_container) conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon) conversationBadgeBg = - requireViewById(com.android.internal.R.id.conversation_icon_badge_bg) + requireViewById(com.android.internal.R.id.conversation_icon_badge_bg) expandBtn = requireViewById(com.android.internal.R.id.expand_button) appName = requireViewById(com.android.internal.R.id.app_name_text) - conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text) + conversationTitleView = + requireViewById( + if (notificationsRedesignTemplates()) com.android.internal.R.id.title + else com.android.internal.R.id.conversation_text + ) } } @@ -68,20 +69,12 @@ class NotificationCallTemplateViewWrapper constructor( override fun updateTransformedTypes() { // This also clears the existing types super.updateTransformedTypes() - addTransformedViews( - appName, - conversationTitleView - ) - addViewsTransformingToSimilar( - conversationIconView, - conversationBadgeBg, - expandBtn - ) + addTransformedViews(appName, conversationTitleView) + addViewsTransformingToSimilar(conversationIconView, conversationBadgeBg, expandBtn) } override fun disallowSingleClick(x: Float, y: Float): Boolean { - val isOnExpandButton = expandBtn.visibility == View.VISIBLE && - isOnView(expandBtn, x, y) + val isOnExpandButton = expandBtn.visibility == View.VISIBLE && isOnView(expandBtn, x, y) return isOnExpandButton || super.disallowSingleClick(x, y) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt index 9d13ab53ec71..6a96fba8f28d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt @@ -17,10 +17,12 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.app.Flags +import android.app.Flags.notificationsRedesignTemplates import android.content.Context import android.graphics.drawable.AnimatedImageDrawable import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import com.android.internal.widget.CachingIconView import com.android.internal.widget.ConversationLayout import com.android.internal.widget.MessagingGroup @@ -53,8 +55,8 @@ class NotificationConversationTemplateViewWrapper( private lateinit var badgeIconView: NotificationRowIconView private lateinit var conversationBadgeBg: View private lateinit var expandBtn: View - private lateinit var expandBtnContainer: View - private lateinit var imageMessageContainer: ViewGroup + private var expandBtnContainer: View? = null + private var imageMessageContainer: ViewGroup? = null private lateinit var messageContainers: ArrayList<MessagingGroup> private lateinit var messagingLinearLayout: MessagingLinearLayout private lateinit var conversationTitleView: View @@ -78,10 +80,14 @@ class NotificationConversationTemplateViewWrapper( conversationBadgeBg = requireViewById(com.android.internal.R.id.conversation_icon_badge_bg) expandBtn = requireViewById(com.android.internal.R.id.expand_button) - expandBtnContainer = requireViewById(com.android.internal.R.id.expand_button_container) + expandBtnContainer = findViewById(com.android.internal.R.id.expand_button_container) importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring) appName = requireViewById(com.android.internal.R.id.app_name_text) - conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text) + conversationTitleView = + requireViewById( + if (notificationsRedesignTemplates()) com.android.internal.R.id.title + else com.android.internal.R.id.conversation_text + ) facePileTop = findViewById(com.android.internal.R.id.conversation_face_pile_top) facePileBottom = findViewById(com.android.internal.R.id.conversation_face_pile_bottom) facePileBottomBg = @@ -133,11 +139,21 @@ class NotificationConversationTemplateViewWrapper( expandable: Boolean, onClickListener: View.OnClickListener, requestLayout: Boolean, - ) = conversationLayout.updateExpandability(expandable, onClickListener) + ) { + if (notificationsRedesignTemplates()) { + super.updateExpandability(expandable, onClickListener, requestLayout) + } else { + conversationLayout.updateExpandability(expandable, onClickListener) + } + } override fun disallowSingleClick(x: Float, y: Float): Boolean { val isOnExpandButton = - expandBtnContainer.visibility == View.VISIBLE && isOnView(expandBtnContainer, x, y) + if (notificationsRedesignTemplates()) { + expandBtn.isVisible && isOnView(expandBtn, x, y) + } else { + expandBtnContainer?.visibility == View.VISIBLE && isOnView(expandBtnContainer, x, y) + } return isOnExpandButton || super.disallowSingleClick(x, y) } @@ -158,7 +174,8 @@ class NotificationConversationTemplateViewWrapper( // and the top level image message container. val containers = messageContainers.asSequence().map { it.messageContainer } + - sequenceOf(imageMessageContainer) + if (notificationsRedesignTemplates()) emptySequence() + else sequenceOf(imageMessageContainer!!) val drawables = containers .flatMap { it.children } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 3d60e03d7ca4..3ff18efeae53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -6686,7 +6686,7 @@ public class NotificationStackScrollLayout static boolean canChildBeCleared(View v) { if (v instanceof ExpandableNotificationRow row) { - if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) { + if (row.areGutsExposed() || !row.hasFinishedInitialization()) { return false; } return row.canViewBeCleared(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 06b989aaab57..08692bea2292 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -1227,13 +1227,17 @@ public class StackScrollAlgorithm { float baseZ = ambientState.getBaseZHeight(); if (SceneContainerFlag.isEnabled()) { - // SceneContainer flags off this logic, and just sets the baseZ because: + // SceneContainer simplifies this logic, because: // - there are no overlapping HUNs anymore, no need for multiplying their shadows // - shadows for HUNs overlapping with the stack are now set from updateHeadsUpStates - // - shadows for HUNs overlapping with the shelf are NOT set anymore, because it only - // happens on AOD/Pulsing, where they're displayed on a black background so a shadow - // wouldn't be visible. - childViewState.setZTranslation(baseZ); + if (child.isPinned() || ambientState.getTrackedHeadsUpRow() == child) { + // set a default elevation on the HUN, which would be overridden + // from updateHeadsUpStates if it is displayed in the shade + childViewState.setZTranslation(baseZ + mPinnedZTranslationExtra); + } else { + // set baseZ for every notification + childViewState.setZTranslation(baseZ); + } } else { if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible && !ambientState.isDozingAndNotPulsing(child) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 5c81c8e22bfc..34b65603542c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import android.content.Context import androidx.annotation.VisibleForTesting import com.android.app.tracing.coroutines.flow.flowName +import com.android.systemui.Flags.glanceableHubV2 import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -87,7 +88,9 @@ import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNoti import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor +import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf +import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.FlowDumperImpl import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.systemui.util.kotlin.sample @@ -299,20 +302,40 @@ constructor( .distinctUntilChanged() .dumpWhileCollecting("configurationBasedDimensions") + private val isOnAnyBouncer: Flow<Boolean> = + anyOf( + keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER).map { it > 0f }, + keyguardTransitionInteractor + .transitionValue( + scene = Scenes.Bouncer, + stateWithoutSceneContainer = PRIMARY_BOUNCER, + ) + .map { it > 0f }, + ) + /** If the user is visually on one of the unoccluded lockscreen states. */ val isOnLockscreen: Flow<Boolean> = - anyOf( - keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f }, - keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f }, - keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER).map { it > 0f }, - keyguardTransitionInteractor - .transitionValue( - scene = Scenes.Bouncer, - stateWithoutSceneContainer = PRIMARY_BOUNCER, - ) - .map { it > 0f }, - keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f }, - ) + if (glanceableHubV2()) { + anyOf( + keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f }, + keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f }, + keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f }, + allOf( + // Exclude bouncer showing over communal hub, as this should not be + // considered + // "lockscreen" + not(communalSceneInteractor.isCommunalVisible), + isOnAnyBouncer, + ), + ) + } else { + anyOf( + keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f }, + keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f }, + keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f }, + isOnAnyBouncer, + ) + } .flowName("isOnLockscreen") .stateIn( scope = applicationScope, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index c52536d2b312..10821dffd394 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -21,7 +21,6 @@ import android.telephony.CarrierConfigManager import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING import com.android.settingslib.SignalIcon.MobileIconGroup -import com.android.settingslib.flags.Flags import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -29,11 +28,13 @@ import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.core.NewStatusBarIcons import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository @@ -85,6 +86,12 @@ interface MobileIconsInteractor { /** Whether the mobile icons can be stacked vertically. */ val isStackable: StateFlow<Boolean> + /** + * Observable for the subscriptionId of the current mobile data connection. Null if we don't + * have a valid subscription id + */ + val activeMobileDataSubscriptionId: StateFlow<Int?> + /** True if the active mobile data subscription has data enabled */ val activeDataConnectionHasDataEnabled: StateFlow<Boolean> @@ -168,6 +175,9 @@ constructor( ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val activeMobileDataSubscriptionId: StateFlow<Int?> = + mobileConnectionsRepo.activeMobileDataSubscriptionId + override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> = mobileConnectionsRepo.activeMobileDataRepository .flatMapLatest { it?.dataEnabled ?: flowOf(false) } @@ -298,10 +308,16 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList()) override val isStackable = - if (Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled) { + if (NewStatusBarIcons.isEnabled && StatusBarRootModernization.isEnabled) { icons.flatMapLatest { icons -> - combine(icons.map { it.isNonTerrestrial }) { - it.size == 2 && it.none { isNonTerrestrial -> isNonTerrestrial } + combine(icons.map { it.signalLevelIcon }) { signalLevelIcons -> + // These are only stackable if: + // - They are cellular + // - There's exactly two + // - They have the same number of levels + signalLevelIcons.filterIsInstance<SignalIconModel.Cellular>().let { + it.size == 2 && it[0].numberOfLevels == it[1].numberOfLevels + } } } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 6176a3e9e281..288e49eac5a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -63,6 +63,8 @@ constructor( @VisibleForTesting val reuseCache = ConcurrentHashMap<Int, Pair<MobileIconViewModel, CoroutineScope>>() + val activeMobileDataSubscriptionId: StateFlow<Int?> = interactor.activeMobileDataSubscriptionId + val subscriptionIdsFlow: StateFlow<List<Int>> = interactor.filteredSubscriptions .mapLatest { subscriptions -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt index a2c2a3cd1507..2c85a5150575 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt @@ -25,7 +25,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconMod import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -43,8 +43,14 @@ constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable() initialValue = false, ) - private val iconViewModelFlow: StateFlow<List<MobileIconViewModelCommon>> = - mobileIconsViewModel.mobileSubViewModels + private val iconViewModelFlow: Flow<List<MobileIconViewModelCommon>> = + combine( + mobileIconsViewModel.mobileSubViewModels, + mobileIconsViewModel.activeMobileDataSubscriptionId, + ) { viewModels, activeSubId -> + // Sort to get the active subscription first, if it's set + viewModels.sortedByDescending { it.subscriptionId == activeSubId } + } val dualSim: DualSim? by hydrator.hydratedStateOf( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt index cd320a12d577..d7348892356d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt @@ -31,6 +31,8 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder +import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipViewBinding +import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModelLegacy import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.core.StatusBarRootModernization @@ -149,6 +151,7 @@ constructor( if (!StatusBarNotifChips.isEnabled && !StatusBarChipsModernization.isEnabled) { val primaryChipViewBinding = OngoingActivityChipBinder.createBinding(primaryChipView) + launch { viewModel.primaryOngoingActivityChip.collect { primaryChipModel -> OngoingActivityChipBinder.bind( @@ -156,18 +159,14 @@ constructor( primaryChipViewBinding, iconViewStore, ) - if (StatusBarRootModernization.isEnabled) { - when (primaryChipModel) { - is OngoingActivityChipModel.Active -> - primaryChipViewBinding.rootView.show( - shouldAnimateChange = true - ) - is OngoingActivityChipModel.Inactive -> - primaryChipViewBinding.rootView.hide( - state = View.GONE, - shouldAnimateChange = primaryChipModel.shouldAnimate, - ) + if (StatusBarRootModernization.isEnabled) { + launch { + bindLegacyPrimaryOngoingActivityChipWithVisibility( + viewModel, + primaryChipModel, + primaryChipViewBinding, + ) } } else { when (primaryChipModel) { @@ -213,12 +212,14 @@ constructor( ) if (StatusBarRootModernization.isEnabled) { - primaryChipViewBinding.rootView.adjustVisibility( - chips.primary.toVisibilityModel() - ) - secondaryChipViewBinding.rootView.adjustVisibility( - chips.secondary.toVisibilityModel() - ) + launch { + bindOngoingActivityChipsWithVisibility( + viewModel, + chips, + primaryChipViewBinding, + secondaryChipViewBinding, + ) + } } else { listener?.onOngoingActivityStatusChanged( hasPrimaryOngoingActivity = @@ -312,6 +313,52 @@ constructor( } } + /** Bind the (legacy) single primary ongoing activity chip with the status bar visibility */ + private suspend fun bindLegacyPrimaryOngoingActivityChipWithVisibility( + viewModel: HomeStatusBarViewModel, + primaryChipModel: OngoingActivityChipModel, + primaryChipViewBinding: OngoingActivityChipViewBinding, + ) { + viewModel.canShowOngoingActivityChips.collectLatest { visible -> + if (!visible) { + primaryChipViewBinding.rootView.hide(shouldAnimateChange = false) + } else { + when (primaryChipModel) { + is OngoingActivityChipModel.Active -> { + primaryChipViewBinding.rootView.show(shouldAnimateChange = true) + } + + is OngoingActivityChipModel.Inactive -> { + primaryChipViewBinding.rootView.hide( + state = View.GONE, + shouldAnimateChange = primaryChipModel.shouldAnimate, + ) + } + } + } + } + } + + /** Bind the primary/secondary chips along with the home status bar's visibility */ + private suspend fun bindOngoingActivityChipsWithVisibility( + viewModel: HomeStatusBarViewModel, + chips: MultipleOngoingActivityChipsModelLegacy, + primaryChipViewBinding: OngoingActivityChipViewBinding, + secondaryChipViewBinding: OngoingActivityChipViewBinding, + ) { + viewModel.canShowOngoingActivityChips.collectLatest { canShow -> + if (!canShow) { + primaryChipViewBinding.rootView.hide(shouldAnimateChange = false) + secondaryChipViewBinding.rootView.hide(shouldAnimateChange = false) + } else { + primaryChipViewBinding.rootView.adjustVisibility(chips.primary.toVisibilityModel()) + secondaryChipViewBinding.rootView.adjustVisibility( + chips.secondary.toVisibilityModel() + ) + } + } + } + private fun SystemEventAnimationState.isAnimatingChip() = when (this) { AnimatingIn, @@ -374,43 +421,42 @@ constructor( if (visibility == View.INVISIBLE || visibility == View.GONE) { return } + alpha = 0f visibility = state } // See CollapsedStatusBarFragment#hide. private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) { + animate().cancel() if (visibility == View.INVISIBLE || visibility == View.GONE) { return } - val v = this - v.animate().cancel() if (!shouldAnimateChange) { - v.alpha = 0f - v.visibility = state + alpha = 0f + visibility = state return } - v.animate() + animate() .alpha(0f) .setDuration(CollapsedStatusBarFragment.FADE_OUT_DURATION.toLong()) .setStartDelay(0) .setInterpolator(Interpolators.ALPHA_OUT) - .withEndAction { v.visibility = state } + .withEndAction { visibility = state } } // See CollapsedStatusBarFragment#show. private fun View.show(shouldAnimateChange: Boolean) { - if (visibility == View.VISIBLE) { + animate().cancel() + if (visibility == View.VISIBLE && alpha >= 1f) { return } - val v = this - v.animate().cancel() - v.visibility = View.VISIBLE + visibility = View.VISIBLE if (!shouldAnimateChange) { - v.alpha = 1f + alpha = 1f return } - v.animate() + animate() .alpha(1f) .setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION.toLong()) .setInterpolator(Interpolators.ALPHA_IN) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt index 465a43fbfb9e..a51da0e4cc73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt @@ -17,9 +17,12 @@ package com.android.systemui.statusbar.pipeline.shared.ui.composable import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -27,23 +30,28 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.android.compose.modifiers.height -import com.android.compose.modifiers.width import com.android.systemui.common.ui.compose.Icon import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModel import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarBaseHeightFiveBarsSp import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarBaseHeightFourBarsSp import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarsLevelIncrementSp import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarsVerticalPaddingSp +import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationCutoutRadiusSp +import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationDiameterSp +import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationHeightSp +import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationHorizontalOffset +import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationVerticalSpacing import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.HorizontalPaddingFiveBarsSp import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.HorizontalPaddingFourBarsSp import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconHeightSp +import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconPaddingSp +import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconSpacingSp import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconWidthFiveBarsSp import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconWidthFourBarsSp import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.SecondaryBarHeightSp @@ -58,15 +66,16 @@ fun StackedMobileIcon(viewModel: StackedMobileIconViewModel, modifier: Modifier val dualSim = viewModel.dualSim ?: return val contentColor = LocalContentColor.current - - Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) { + val padding = with(LocalDensity.current) { IconPaddingSp.toDp() } + val horizontalArrangement = with(LocalDensity.current) { spacedBy(IconSpacingSp.toDp()) } + + Row( + horizontalArrangement = horizontalArrangement, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier.padding(horizontal = padding), + ) { viewModel.networkTypeIcon?.let { - Icon( - it, - tint = contentColor, - modifier = - Modifier.height { IconHeightSp.roundToPx() }.padding(start = 1.dp, end = 2.dp), - ) + Icon(it, tint = contentColor, modifier = Modifier.fillMaxHeight()) } StackedMobileIcon(dualSim, contentColor) @@ -79,23 +88,23 @@ private fun StackedMobileIcon( color: Color, modifier: Modifier = Modifier, ) { - val maxNumberOfLevels = - max(viewModel.primary.numberOfLevels, viewModel.secondary.numberOfLevels) - val dimensions = if (maxNumberOfLevels == 6) FiveBarsDimensions else FourBarsDimensions + // Removing 1 to get the real number of bars + val numberOfBars = max(viewModel.primary.numberOfLevels, viewModel.secondary.numberOfLevels) - 1 + val dimensions = if (numberOfBars == 5) FiveBarsDimensions else FourBarsDimensions val iconSize = with(LocalDensity.current) { dimensions.totalWidth.toDp() to IconHeightSp.toDp() } - Canvas(modifier.size(width = iconSize.first, height = iconSize.second)) { + Canvas(modifier.width(iconSize.first).height(iconSize.second)) { val verticalPaddingPx = BarsVerticalPaddingSp.roundToPx() val horizontalPaddingPx = dimensions.barsHorizontalPadding.roundToPx() - val totalPaddingWidthPx = horizontalPaddingPx * (maxNumberOfLevels - 1) + val totalPaddingWidthPx = horizontalPaddingPx * (numberOfBars - 1) - val barWidthPx = (size.width - totalPaddingWidthPx) / maxNumberOfLevels + val barWidthPx = (size.width - totalPaddingWidthPx) / numberOfBars val dotHeightPx = SecondaryBarHeightSp.toPx() val baseBarHeightPx = dimensions.barBaseHeight.toPx() var xOffsetPx = 0f - for (bar in 1..maxNumberOfLevels) { + for (bar in 1..numberOfBars) { // Bottom dots representing secondary sim val dotYOffsetPx = size.height - dotHeightPx if (bar <= viewModel.secondary.numberOfLevels) { @@ -123,6 +132,10 @@ private fun StackedMobileIcon( xOffsetPx += barWidthPx + horizontalPaddingPx } + + if (viewModel.primary.showExclamationMark) { + drawExclamationCutout(color) + } } } @@ -143,6 +156,39 @@ private fun DrawScope.drawMobileIconBar( ) } +private fun DrawScope.drawExclamationCutout(color: Color) { + // Exclamation mark is bottom aligned with the canvas + val exclamationDiameterPx = ExclamationDiameterSp.toPx() + val exclamationRadiusPx = ExclamationDiameterSp.toPx() / 2 + val exclamationTotalHeight = + ExclamationHeightSp.toPx() + ExclamationVerticalSpacing.toPx() + exclamationDiameterPx + val exclamationDotCenter = + Offset(size.width - ExclamationHorizontalOffset.toPx(), size.height - exclamationRadiusPx) + val exclamationMarkTopLeft = + Offset(exclamationDotCenter.x - exclamationRadiusPx, size.height - exclamationTotalHeight) + val exclamationCornerRadius = CornerRadius(exclamationRadiusPx) + val cutoutCenter = Offset(exclamationDotCenter.x, size.height - (exclamationTotalHeight / 2)) + + // Transparent cutout + drawCircle( + color = Color.Transparent, + radius = ExclamationCutoutRadiusSp.toPx(), + center = cutoutCenter, + blendMode = BlendMode.SrcIn, + ) + + // Top bar for the exclamation mark + drawRoundRect( + color = color, + topLeft = exclamationMarkTopLeft, + size = Size(exclamationDiameterPx, ExclamationHeightSp.toPx()), + cornerRadius = exclamationCornerRadius, + ) + + // Bottom circle for the exclamation mark + drawCircle(color = color, center = exclamationDotCenter, radius = exclamationRadiusPx) +} + private abstract class BarsDependentDimensions( val totalWidth: TextUnit, val barsHorizontalPadding: TextUnit, @@ -166,10 +212,19 @@ private object FiveBarsDimensions : private object StackedMobileIconDimensions { // Common dimensions val IconHeightSp = 12.sp + val IconPaddingSp = 4.sp + val IconSpacingSp = 2.sp val BarsVerticalPaddingSp = 1.5.sp val BarsLevelIncrementSp = 1.sp val SecondaryBarHeightSp = 3.sp + // Exclamation cutout dimensions + val ExclamationCutoutRadiusSp = 5.sp + val ExclamationDiameterSp = 1.5.sp + val ExclamationHeightSp = 4.5.sp + val ExclamationVerticalSpacing = 1.sp + val ExclamationHorizontalOffset = 1.sp + // Dimensions dependant on the number of total bars val IconWidthFiveBarsSp = 18.5.sp val IconWidthFourBarsSp = 16.sp diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt index 9ae2cb203d91..807e90567eb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt @@ -149,6 +149,9 @@ interface HomeStatusBarViewModel : Activatable { */ val isHomeStatusBarAllowedByScene: StateFlow<Boolean> + /** True if the home status bar is showing, and there is no HUN happening */ + val canShowOngoingActivityChips: Flow<Boolean> + /** True if the operator name view is not hidden due to HUN or other visibility state */ val shouldShowOperatorNameView: Flow<Boolean> val isClockVisible: Flow<VisibilityModel> @@ -412,6 +415,15 @@ constructor( ) .flowOn(bgDispatcher) + override val canShowOngoingActivityChips: Flow<Boolean> = + combine( + isHomeStatusBarAllowed, + keyguardInteractor.isSecureCameraActive, + headsUpNotificationInteractor.statusBarHeadsUpStatus, + ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState -> + isHomeStatusBarAllowed && !isSecureCameraActive && !headsUpState.isPinned + } + override val isClockVisible: Flow<VisibilityModel> = combine( shouldHomeStatusBarBeVisible, diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt index ef29a387e06f..3bd8af690763 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt @@ -18,12 +18,20 @@ package com.android.systemui.user.domain.interactor import android.os.UserHandle import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn @SysUISingleton -class UserLockedInteractor @Inject constructor(val userRepository: UserRepository) { +class UserLockedInteractor +@Inject +constructor( + @Background val backgroundDispatcher: CoroutineDispatcher, + val userRepository: UserRepository, +) { fun isUserUnlocked(userHandle: UserHandle?): Flow<Boolean> = - userRepository.isUserUnlocked(userHandle) + userRepository.isUserUnlocked(userHandle).flowOn(backgroundDispatcher) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt index 2e634390679a..c2a495d13c02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt @@ -64,6 +64,12 @@ class FontInterpolatorTest : SysuiTestCase() { "'wght' 500, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f, 0.5f), ) + + // Ensure axes rounded correctly to nearest step + assertSameAxes( + "'wght' 490, 'ital' 0.5, 'GRAD' 446", + interp.lerp(startFont, endFont, 0.492f, 0.492f), + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 4ff09d3bc6af..81213caaa5f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -45,6 +45,9 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; @@ -74,10 +77,14 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -123,10 +130,13 @@ public class ShadeListBuilderTest extends SysuiTestCase { private CollectionReadyForBuildListener mReadyForBuildListener; private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>(); private List<NotificationEntry> mEntrySet = new ArrayList<>(); - private List<ListEntry> mBuiltList = new ArrayList<>(); + private List<PipelineEntry> mBuiltList = new ArrayList<>(); private TestableStabilityManager mStabilityManager; private TestableNotifFilter mFinalizeFilter; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private Map<String, Integer> mNextIdMap = new ArrayMap<>(); private int mNextRank = 0; @@ -561,6 +571,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void testFilter_resetsInitalizationTime() { // GIVEN a NotifFilter that filters out a specific package NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1)); @@ -584,6 +595,31 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + public void testFilter_resetsInitializationTime_onRow() throws Exception { + // GIVEN a NotifFilter that filters out a specific package + NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1)); + mListBuilder.addFinalizeFilter(filter1); + + // GIVEN a notification that was initialized 1 second ago that will be filtered out + final NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(PACKAGE_1) + .setId(nextId(PACKAGE_1)) + .setRank(nextRank()) + .build(); + entry.setRow(new NotificationTestHelper(mContext, mDependency).createRow()); + entry.getRow().setInitializationTime(SystemClock.elapsedRealtime() - 1000); + assertTrue(entry.getRow().hasFinishedInitialization()); + + // WHEN the pipeline is kicked off + mReadyForBuildListener.onBuildList(singletonList(entry), "test"); + mPipelineChoreographer.runIfScheduled(); + + // THEN the entry's initialization time is reset + assertFalse(entry.getRow().hasFinishedInitialization()); + } + + @Test public void testNotifFiltersCanBePreempted() { // GIVEN two notif filters NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2)); @@ -687,26 +723,26 @@ public class ShadeListBuilderTest extends SysuiTestCase { @Test public void testNotifSectionsChildrenUpdated() { - ArrayList<ListEntry> pkg1Entries = new ArrayList<>(); - ArrayList<ListEntry> pkg2Entries = new ArrayList<>(); - ArrayList<ListEntry> pkg3Entries = new ArrayList<>(); + ArrayList<PipelineEntry> pkg1Entries = new ArrayList<>(); + ArrayList<PipelineEntry> pkg2Entries = new ArrayList<>(); + ArrayList<PipelineEntry> pkg3Entries = new ArrayList<>(); final NotifSectioner pkg1Sectioner = spy(new PackageSectioner(PACKAGE_1) { @Override - public void onEntriesUpdated(List<ListEntry> entries) { + public void onEntriesUpdated(List<PipelineEntry> entries) { super.onEntriesUpdated(entries); pkg1Entries.addAll(entries); } }); final NotifSectioner pkg2Sectioner = spy(new PackageSectioner(PACKAGE_2) { @Override - public void onEntriesUpdated(List<ListEntry> entries) { + public void onEntriesUpdated(List<PipelineEntry> entries) { super.onEntriesUpdated(entries); pkg2Entries.addAll(entries); } }); final NotifSectioner pkg3Sectioner = spy(new PackageSectioner(PACKAGE_3) { @Override - public void onEntriesUpdated(List<ListEntry> entries) { + public void onEntriesUpdated(List<PipelineEntry> entries) { super.onEntriesUpdated(entries); pkg3Entries.addAll(entries); } @@ -2442,7 +2478,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { mBuiltList.size()); for (int i = 0; i < expectedEntries.length; i++) { - ListEntry outEntry = mBuiltList.get(i); + PipelineEntry outEntry = mBuiltList.get(i); ExpectedEntry expectedEntry = expectedEntries[i]; if (expectedEntry instanceof ExpectedNotif) { @@ -2617,7 +2653,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Override - public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) { + public int compare(@NonNull PipelineEntry o1, @NonNull PipelineEntry o2) { boolean contains1 = mPreferredPackages.contains( o1.getRepresentativeEntry().getSbn().getPackageName()); boolean contains2 = mPreferredPackages.contains( @@ -2655,37 +2691,37 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Override - public boolean isInSection(ListEntry entry) { + public boolean isInSection(PipelineEntry entry) { return mPackages.contains(entry.getRepresentativeEntry().getSbn().getPackageName()); } } private static class RecordingOnBeforeTransformGroupsListener implements OnBeforeTransformGroupsListener { - List<ListEntry> mEntriesReceived; + List<PipelineEntry> mEntriesReceived; @Override - public void onBeforeTransformGroups(List<ListEntry> list) { + public void onBeforeTransformGroups(List<PipelineEntry> list) { mEntriesReceived = new ArrayList<>(list); } } private static class RecordingOnBeforeSortListener implements OnBeforeSortListener { - List<ListEntry> mEntriesReceived; + List<PipelineEntry> mEntriesReceived; @Override - public void onBeforeSort(List<ListEntry> list) { + public void onBeforeSort(List<PipelineEntry> list) { mEntriesReceived = new ArrayList<>(list); } } private static class RecordingOnBeforeRenderListener implements OnBeforeRenderListListener { - List<ListEntry> mEntriesReceived; + List<PipelineEntry> mEntriesReceived; @Override - public void onBeforeRenderList(List<ListEntry> list) { + public void onBeforeRenderList(List<PipelineEntry> list) { mEntriesReceived = new ArrayList<>(list); } } @@ -2764,7 +2800,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Override - public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) { + public boolean isEntryReorderingAllowed(@NonNull PipelineEntry entry) { return mAllowEntryReodering; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 1b5353127f25..24d8d1cc8fae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -75,6 +75,7 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -589,6 +590,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void testGetIsNonblockable() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 47238fedee4d..c874bc6056c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -36,6 +36,7 @@ import com.android.internal.widget.NotificationActionListLayout import com.android.internal.widget.NotificationExpandButton import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.FeedbackIcon +import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.shared.NotificationBundleUi @@ -82,6 +83,7 @@ class NotificationContentViewTest : SysuiTestCase() { fakeParent = spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE }) val mockEntry = createMockNotificationEntry() + val mockEntryAdapter = createMockNotificationEntryAdapter() row = spy( when (NotificationBundleUi.isEnabled) { @@ -92,6 +94,7 @@ class NotificationContentViewTest : SysuiTestCase() { UserHandle.CURRENT ).apply { entry = mockEntry + entryAdapter = mockEntryAdapter } } false -> { @@ -611,6 +614,7 @@ class NotificationContentViewTest : SysuiTestCase() { whenever(this.entry).thenReturn(notificationEntry) whenever(this.context).thenReturn(mContext) whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {}) + whenever(this.entryAdapter).thenReturn(createMockNotificationEntryAdapter()) } private fun createMockNotificationEntry() = @@ -624,6 +628,9 @@ class NotificationContentViewTest : SysuiTestCase() { whenever(sbnMock.user).thenReturn(userMock) } + private fun createMockNotificationEntryAdapter() = + mock<EntryAdapter>() + private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout { val outerLayout = LinearLayout(mContext) val innerLayout = LinearLayout(mContext) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index 3d4c90140adb..99b99ee1860b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -427,7 +427,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) .setImportance(NotificationManager.IMPORTANCE_HIGH) .build() - whenever(row.getIsNonblockable()).thenReturn(false) whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) val statusBarNotification = entry.sbn gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -463,7 +462,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.getIsNonblockable()).thenReturn(false) val statusBarNotification = row.entry.sbn val entry = row.entry gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -499,7 +497,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.getIsNonblockable()).thenReturn(false) val statusBarNotification = row.entry.sbn val entry = row.entry gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -566,7 +563,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { ): NotificationMenuRowPlugin.MenuItem { val menuRow: NotificationMenuRowPlugin = NotificationMenuRow(mContext, peopleNotificationIdentifier) - menuRow.createMenu(row, row!!.entry.sbn) + menuRow.createMenu(row) val menuItem = menuRow.getLongpressMenuItem(mContext) Assert.assertNotNull(menuItem) return menuItem diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 6ac20d40f2dc..955de273c426 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -675,7 +675,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testClearNotifications_clearAllInProgress() { ExpandableNotificationRow row = createClearableRow(); - when(row.getEntry().hasFinishedInitialization()).thenReturn(true); + when(row.hasFinishedInitialization()).thenReturn(true); doReturn(true).when(mStackScroller).isVisible(row); mStackScroller.addContainerView(row); diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt index a1122c3cbcd2..a553b176c34a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt @@ -18,10 +18,12 @@ package com.android.systemui.unfold import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager.FoldStateListener +import android.platform.test.annotations.DisableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.foldedDeviceStateList import com.android.systemui.halfFoldedDeviceState @@ -46,6 +48,7 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @SmallTest +@DisableFlags(Flags.FLAG_UNFOLD_LATENCY_TRACKING_FIX) class UnfoldLatencyTrackerTest : SysuiTestCase() { @Mock lateinit var latencyTracker: LatencyTracker diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt index 8d4db8b74061..8a6f68c5da68 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.panels.domain.interactor +import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.core.FakeLogBuffer @@ -29,6 +30,7 @@ val Kosmos.iconTilesInteractor by defaultLargeTilesRepository, currentTilesInteractor, qsPreferencesInteractor, + uiEventLoggerFake, largeTileSpanRepository, FakeLogBuffer.Factory.create(), applicationCoroutineScope, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java index 562ac0c15a0b..804ec9f29926 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java @@ -56,7 +56,7 @@ public class NotificationEntryBuilder { private StatusBarNotification mSbn = null; /* ListEntry properties */ - private GroupEntry mParent; + private PipelineEntry mParent; private NotifSection mNotifSection; /* If set, use this creation time instead of mClock.uptimeMillis */ @@ -91,7 +91,7 @@ public class NotificationEntryBuilder { } /** Update an the parent on an existing entry */ - public static void setNewParent(NotificationEntry entry, GroupEntry parent) { + public static void setNewParent(NotificationEntry entry, PipelineEntry parent) { entry.setParent(parent); } @@ -135,7 +135,7 @@ public class NotificationEntryBuilder { /** * Sets the parent. */ - public NotificationEntryBuilder setParent(@Nullable GroupEntry parent) { + public NotificationEntryBuilder setParent(@Nullable PipelineEntry parent) { mParent = parent; return this; } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt index d09d010cba2e..8ff7c7d01fb3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt @@ -76,6 +76,7 @@ object OngoingCallTestHelper { promotedContent: PromotedNotificationContentModel? = null, contentIntent: PendingIntent? = null, uid: Int = DEFAULT_UID, + appName: String = "Fake name", ) { if (StatusBarChipsModernization.isEnabled) { activeNotificationListRepository.addNotif( @@ -87,6 +88,7 @@ object OngoingCallTestHelper { contentIntent = contentIntent, promotedContent = promotedContent, uid = uid, + appName = appName, ) ) } else { @@ -96,6 +98,7 @@ object OngoingCallTestHelper { notificationIcon = statusBarChipIconView, intent = contentIntent, notificationKey = key, + appName = appName, promotedContent = promotedContent, ) ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index 9b6f205fba72..8fa82cad5c32 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -58,6 +58,8 @@ class FakeMobileIconsInteractor( override val defaultDataSubId: MutableStateFlow<Int?> = MutableStateFlow(DEFAULT_DATA_SUB_ID) + override val activeMobileDataSubscriptionId: MutableStateFlow<Int?> = MutableStateFlow(null) + private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false) override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt new file mode 100644 index 000000000000..880ba5eee5d2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.statusbar.pipeline.mobile.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.stackedMobileIconViewModel: StackedMobileIconViewModel by + Kosmos.Fixture { StackedMobileIconViewModel(mobileIconsViewModel) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt index fd955089cdc7..933c351679a4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt @@ -17,7 +17,10 @@ package com.android.systemui.user.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.user.data.repository.userRepository val Kosmos.userLockedInteractor by - Kosmos.Fixture { UserLockedInteractor(userRepository = userRepository) } + Kosmos.Fixture { + UserLockedInteractor(backgroundDispatcher = testDispatcher, userRepository = userRepository) + } diff --git a/packages/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp index edb22c0e7aa0..c53123359872 100644 --- a/packages/Vcn/framework-b/Android.bp +++ b/packages/Vcn/framework-b/Android.bp @@ -77,8 +77,7 @@ framework_connectivity_b_defaults_soong_config { ], soong_config_variables: { is_vcn_in_mainline: { - //TODO: b/380155299 Make it Baklava when aidl tool can understand it - min_sdk_version: "current", + min_sdk_version: "36", static_libs: ["android.net.vcn.flags-aconfig-java"], apex_available: ["com.android.tethering"], diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 65550f2b4273..ccbc46fdb03b 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -121,6 +121,7 @@ java_library { name: "ravenwood-helper-framework-runtime", srcs: [ "runtime-helper-src/framework/**/*.java", + ":framework-graphics-srcs", ], static_libs: [ "ravenwood-runtime-common", @@ -278,6 +279,7 @@ cc_library_host_shared { cc_library_host_shared { name: "libravenwood_runtime", defaults: ["ravenwood_jni_defaults"], + header_libs: ["libicuuc_headers"], srcs: [ "runtime-jni/ravenwood_runtime.cpp", "runtime-jni/ravenwood_os_constants.cpp", @@ -372,6 +374,13 @@ platform_compat_config { visibility: ["//visibility:private"], } +java_library { + name: "ext-ravenwood", + installable: false, + static_libs: ["ext"], + visibility: ["//visibility:private"], +} + filegroup { name: "ravenwood-data", device_common_srcs: [ @@ -637,6 +646,7 @@ android_ravenwood_libgroup { libs: [ "100-framework-minus-apex.ravenwood", "200-kxml2-android", + "ext-ravenwood", "ravenwood-runtime-common-ravenwood", diff --git a/ravenwood/Framework.bp b/ravenwood/Framework.bp index 71496b0d5766..e36677189e02 100644 --- a/ravenwood/Framework.bp +++ b/ravenwood/Framework.bp @@ -419,11 +419,13 @@ java_genrule { "--out-impl-jar $(location ravenwood.jar) " + "--in-jar $(location :framework-graphics.impl{.jar}) " + - "--policy-override-file $(location :ravenwood-common-policies) ", + "--policy-override-file $(location :ravenwood-common-policies) " + + "--policy-override-file $(location :framework-graphics-ravenwood-policies) ", srcs: [ ":framework-graphics.impl{.jar}", ":ravenwood-common-policies", + ":framework-graphics-ravenwood-policies", ":ravenwood-standard-options", ], out: [ diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java index a3326337d485..9e6b12f60add 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java @@ -230,6 +230,16 @@ public class RavenwoodContext extends RavenwoodBaseContext { return mAppContext; } + @Override + public boolean isRestricted() { + return false; + } + + @Override + public boolean canLoadUnsafeResources() { + return true; + } + /** * Wrap the given {@link Supplier} to become memoized. * diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java index a208d6dce2ce..7e935d0451ae 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java @@ -48,6 +48,7 @@ public final class RavenwoodNativeLoader { android.content.res.AssetManager.class, android.content.res.StringBlock.class, android.content.res.XmlBlock.class, + android.text.AndroidCharacter.class, }; /** @@ -61,15 +62,49 @@ public final class RavenwoodNativeLoader { android.graphics.Path.class, android.graphics.Color.class, android.graphics.ColorSpace.class, + android.graphics.Bitmap.class, + android.graphics.BitmapFactory.class, + android.graphics.BitmapRegionDecoder.class, + android.graphics.Camera.class, + android.graphics.Canvas.class, + android.graphics.CanvasProperty.class, + android.graphics.ColorFilter.class, + android.graphics.DrawFilter.class, + android.graphics.FontFamily.class, + android.graphics.Gainmap.class, + android.graphics.ImageDecoder.class, + android.graphics.MaskFilter.class, + android.graphics.NinePatch.class, + android.graphics.Paint.class, + android.graphics.PathEffect.class, + android.graphics.PathIterator.class, + android.graphics.PathMeasure.class, + android.graphics.Picture.class, + android.graphics.RecordingCanvas.class, + android.graphics.Region.class, + android.graphics.RenderNode.class, + android.graphics.Shader.class, + android.graphics.RenderEffect.class, + android.graphics.Typeface.class, + android.graphics.YuvImage.class, + android.graphics.fonts.Font.class, + android.graphics.fonts.FontFamily.class, + android.graphics.text.LineBreaker.class, + android.graphics.text.MeasuredText.class, + android.graphics.text.TextRunShaper.class, + android.graphics.text.GraphemeBreak.class, + android.util.PathParser.class, }; /** * Extra strings needed to pass to register_android_graphics_classes(). * - * `android.graphics.Graphics` is not actually a class, so we just hardcode it here. + * Several entries are not actually a class, so we just hardcode them here. */ public final static String[] GRAPHICS_EXTRA_INIT_PARAMS = new String[] { - "android.graphics.Graphics" + "android.graphics.Graphics", + "android.graphics.ByteBufferStreamAdaptor", + "android.graphics.CreateJavaOutputStreamAdaptor" }; private RavenwoodNativeLoader() { diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index ae88bb234e9d..f205d238c693 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -43,6 +43,7 @@ import android.app.UiAutomation; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Resources; +import android.graphics.Typeface; import android.icu.util.ULocale; import android.os.Binder; import android.os.Build; @@ -246,6 +247,13 @@ public class RavenwoodRuntimeEnvironmentController { // Do the basic set up for the android sysprops. RavenwoodSystemProperties.initialize(); + // Set ICU data file + String icuData = RavenwoodCommonUtils.getRavenwoodRuntimePath() + + "ravenwood-data/" + + RavenwoodRuntimeNative.getIcuDataName() + + ".dat"; + RavenwoodRuntimeNative.setSystemProperty("ro.icu.data.path", icuData); + // Enable all log levels for native logging, until we'll have a way to change the native // side log level at runtime. // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()), @@ -268,6 +276,11 @@ public class RavenwoodRuntimeEnvironmentController { Objects.requireNonNull(Build.TYPE); Objects.requireNonNull(Build.VERSION.SDK); + // Fonts can only be initialized once + Typeface.init(); + Typeface.loadPreinstalledSystemFontMap(); + Typeface.loadNativeSystemFonts(); + System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1"); // This will let AndroidJUnit4 use the original runner. System.setProperty("android.junit.runner", diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java index 96aed4b3401d..d5a96ddc3a98 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java @@ -20,6 +20,7 @@ import com.android.ravenwood.common.JvmWorkaround; import java.io.FileDescriptor; import java.util.LinkedHashMap; import java.util.Map; +import java.util.regex.Pattern; /** * Class to host APIs that exist in libcore, but not in standard JRE. @@ -46,4 +47,22 @@ public class RavenwoodJdkPatch { final var it = map.entrySet().iterator(); return it.hasNext() ? it.next() : null; } + + /** + * Implements Pattern.compile(String) + * + * ART always assumes UNICODE_CHARACTER_CLASS is set. + */ + public static Pattern compilePattern(String regex) { + return Pattern.compile(regex, Pattern.UNICODE_CHARACTER_CLASS); + } + + /** + * Implements Pattern.compile(String, int) + * + * ART always assumes UNICODE_CHARACTER_CLASS is set. + */ + public static Pattern compilePattern(String regex, int flag) { + return Pattern.compile(regex, flag | Pattern.UNICODE_CHARACTER_CLASS); + } } diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java index acbcdf1926db..0d82a8691881 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java @@ -66,6 +66,8 @@ public class RavenwoodRuntimeNative { public static native int gettid(); + public static native String getIcuDataName(); + public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence); } diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/CloseGuard.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/CloseGuard.java new file mode 100644 index 000000000000..82bab64f22f3 --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/CloseGuard.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2010 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 dalvik.system; + +/** + * A no-op copy of libcore/dalvik/src/main/java/dalvik/system/CloseGuard.java + */ +public final class CloseGuard { + + /** + * Returns a CloseGuard instance. {@code #open(String)} can be used to set + * up the instance to warn on failure to close. + * + * @return {@link CloseGuard} instance. + * + * @hide + */ + public static CloseGuard get() { + return new CloseGuard(); + } + + /** + * Enables/disables stack capture and tracking. A call stack is captured + * during open(), and open/close events are reported to the Tracker, only + * if enabled is true. If a stack trace was captured, the {@link + * #getReporter() reporter} is informed of unclosed resources; otherwise a + * one-line warning is logged. + * + * @param enabled whether stack capture and tracking is enabled. + * + * @hide + */ + public static void setEnabled(boolean enabled) { + } + + /** + * True if CloseGuard stack capture and tracking are enabled. + * + * @hide + */ + public static boolean isEnabled() { + return false; + } + + /** + * Used to replace default Reporter used to warn of CloseGuard + * violations when stack tracking is enabled. Must be non-null. + * + * @param rep replacement for default Reporter. + * + * @hide + */ + public static void setReporter(Reporter rep) { + if (rep == null) { + throw new NullPointerException("reporter == null"); + } + } + + /** + * Returns non-null CloseGuard.Reporter. + * + * @return CloseGuard's Reporter. + * + * @hide + */ + public static Reporter getReporter() { + return null; + } + + /** + * Sets the {@link Tracker} that is notified when resources are allocated and released. + * The Tracker is invoked only if CloseGuard {@link #isEnabled()} held when {@link #open()} + * was called. A null argument disables tracking. + * + * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so + * MUST NOT be used for any other purposes. + * + * @hide + */ + public static void setTracker(Tracker tracker) { + } + + /** + * Returns {@link #setTracker(Tracker) last Tracker that was set}, or null to indicate + * there is none. + * + * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so + * MUST NOT be used for any other purposes. + * + * @hide + */ + public static Tracker getTracker() { + return null; + } + + private CloseGuard() {} + + /** + * {@code open} initializes the instance with a warning that the caller + * should have explicitly called the {@code closer} method instead of + * relying on finalization. + * + * @param closer non-null name of explicit termination method. Printed by warnIfOpen. + * @throws NullPointerException if closer is null. + * + * @hide + */ + public void open(String closer) { + openWithCallSite(closer, null /* callsite */); + } + + /** + * Like {@link #open(String)}, but with explicit callsite string being passed in for better + * performance. + * <p> + * This only has better performance than {@link #open(String)} if {@link #isEnabled()} returns {@code true}, which + * usually shouldn't happen on release builds. + * + * @param closer Non-null name of explicit termination method. Printed by warnIfOpen. + * @param callsite Non-null string uniquely identifying the callsite. + * + * @hide + */ + public void openWithCallSite(String closer, String callsite) { + } + + // We keep either an allocation stack containing the closer String or, when + // in disabled state, just the closer String. + // We keep them in a single field only to minimize overhead. + private Object /* String or Throwable */ closerNameOrAllocationInfo; + + /** + * Marks this CloseGuard instance as closed to avoid warnings on + * finalization. + * + * @hide + */ + public void close() { + } + + /** + * Logs a warning if the caller did not properly cleanup by calling an + * explicit close method before finalization. If CloseGuard was enabled + * when the CloseGuard was created, passes the stacktrace associated with + * the allocation to the current reporter. If it was not enabled, it just + * directly logs a brief message. + * + * @hide + */ + public void warnIfOpen() { + } + + + /** + * Interface to allow customization of tracking behaviour. + * + * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so + * MUST NOT be used for any other purposes. + * + * @hide + */ + public interface Tracker { + void open(Throwable allocationSite); + void close(Throwable allocationSite); + } + + /** + * Interface to allow customization of reporting behavior. + * @hide + */ + public interface Reporter { + /** + * + * @hide + */ + void report(String message, Throwable allocationSite); + + /** + * + * @hide + */ + default void report(String message) {} + } +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoBridge.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoBridge.java new file mode 100644 index 000000000000..2a1ee2542982 --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoBridge.java @@ -0,0 +1,63 @@ +/* + * 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 libcore.io; + +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.IOException; + +public class IoBridge { + + public static void closeAndSignalBlockedThreads(FileDescriptor fd) throws IOException { + if (fd == null) { + return; + } + try { + Os.close(fd); + } catch (ErrnoException errnoException) { + throw errnoException.rethrowAsIOException(); + } + } + + public static FileDescriptor open(String path, int flags) throws FileNotFoundException { + FileDescriptor fd = null; + try { + fd = Os.open(path, flags, 0666); + // Posix open(2) fails with EISDIR only if you ask for write permission. + // Java disallows reading directories too.f + if (OsConstants.S_ISDIR(Os.fstat(fd).st_mode)) { + throw new ErrnoException("open", OsConstants.EISDIR); + } + return fd; + } catch (ErrnoException errnoException) { + try { + if (fd != null) { + closeAndSignalBlockedThreads(fd); + } + } catch (IOException ignored) { + } + FileNotFoundException ex = new FileNotFoundException(path + ": " + + errnoException.getMessage()); + ex.initCause(errnoException); + throw ex; + } + } +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java index ad86135de32e..cf1a5138cbc6 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java @@ -35,6 +35,11 @@ public class NativeAllocationRegistry { return new NativeAllocationRegistry(classLoader, freeFunction, size); } + public static NativeAllocationRegistry createNonmalloced( + Class clazz, long freeFunction, long size) { + return new NativeAllocationRegistry(clazz.getClassLoader(), freeFunction, size); + } + public static NativeAllocationRegistry createMalloced( ClassLoader classLoader, long freeFunction, long size) { return new NativeAllocationRegistry(classLoader, freeFunction, size); @@ -45,6 +50,11 @@ public class NativeAllocationRegistry { return new NativeAllocationRegistry(classLoader, freeFunction, 0); } + public static NativeAllocationRegistry createMalloced( + Class clazz, long freeFunction, long size) { + return new NativeAllocationRegistry(clazz.getClassLoader(), freeFunction, size); + } + public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) { if (size < 0) { throw new IllegalArgumentException("Invalid native allocation size: " + size); @@ -52,6 +62,37 @@ public class NativeAllocationRegistry { mFreeFunction = freeFunction; } + private class CleanerThunk implements Runnable { + private long nativePtr; + + public CleanerThunk() { + nativePtr = 0; + } + + public void setNativePtr(long ptr) { + nativePtr = ptr; + } + + @Override + public void run() { + if (nativePtr != 0) { + applyFreeFunction(mFreeFunction, nativePtr); + } + } + } + + private static class CleanableRunner implements Runnable { + private final Cleaner.Cleanable mCleanable; + + public CleanableRunner(Cleaner.Cleanable cleanable) { + mCleanable = cleanable; + } + + public void run() { + mCleanable.clean(); + } + } + public Runnable registerNativeAllocation(Object referent, long nativePtr) { if (referent == null) { throw new IllegalArgumentException("referent is null"); @@ -60,13 +101,25 @@ public class NativeAllocationRegistry { throw new IllegalArgumentException("nativePtr is null"); } - final Runnable releaser = () -> { - RavenwoodRuntimeNative.applyFreeFunction(mFreeFunction, nativePtr); - }; - sCleaner.register(referent, releaser); + final CleanerThunk thunk; + final CleanableRunner result; + try { + thunk = new CleanerThunk(); + final var cleanable = sCleaner.register(referent, thunk); + result = new CleanableRunner(cleanable); + } catch (VirtualMachineError vme /* probably OutOfMemoryError */) { + applyFreeFunction(mFreeFunction, nativePtr); + throw vme; + } + // Enable the cleaner only after we can no longer throw anything, including OOME. + thunk.setNativePtr(nativePtr); // Ensure that cleaner doesn't get invoked before we enable it. Reference.reachabilityFence(referent); - return releaser; + return result; + } + + public static void applyFreeFunction(long freeFunction, long nativePtr) { + RavenwoodRuntimeNative.applyFreeFunction(freeFunction, nativePtr); } } diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp index 8d8ed7119e84..01ebdc953539 100644 --- a/ravenwood/runtime-jni/ravenwood_runtime.cpp +++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp @@ -20,6 +20,7 @@ #include <sys/syscall.h> #include <unistd.h> #include <utils/misc.h> +#include <unicode/utypes.h> #include <string> @@ -183,6 +184,10 @@ static jint Linux_gettid(JNIEnv* env, jobject) { return syscall(__NR_gettid); } +static jstring getIcuDataName(JNIEnv* env, jclass clazz) { + return env->NewStringUTF(U_ICUDATA_NAME); +} + // ---- Registration ---- extern void register_android_system_OsConstants(JNIEnv* env); @@ -201,6 +206,7 @@ static const JNINativeMethod sMethods[] = { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv }, { "getpid", "()I", (void*)Linux_getpid }, { "gettid", "()I", (void*)Linux_gettid }, + { "getIcuDataName", "()Ljava/lang/String;", (void*)getIcuDataName }, }; extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index e202d0ecfa23..7462cc2f384a 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -259,6 +259,8 @@ android.database.sqlite.SQLiteClosable android.database.sqlite.SQLiteException android.text.TextUtils +android.text.Html +android.text.HtmlToSpannedConverter android.accounts.Account @@ -278,6 +280,10 @@ android.graphics.PointF android.graphics.Rect android.graphics.RectF +android.graphics.fonts.SystemFonts + +android.graphics.text.LineBreakConfig + android.content.ContentProvider android.app.ActivityManager @@ -383,3 +389,228 @@ android.app.compat.* com.android.server.compat.* com.android.internal.compat.* android.app.AppCompatCallbacks +android.graphics.AvoidXfermode +android.graphics.BLASTBufferQueue +android.graphics.BaseCanvas +android.graphics.BaseRecordingCanvas +android.graphics.Bitmap +android.graphics.BitmapFactory +android.graphics.BitmapRegionDecoder +android.graphics.BitmapShader +android.graphics.BlendMode +android.graphics.BlendModeColorFilter +android.graphics.BlurMaskFilter +android.graphics.Camera +android.graphics.Canvas +android.graphics.CanvasProperty +android.graphics.ColorFilter +android.graphics.ColorMatrix +android.graphics.ColorMatrixColorFilter +android.graphics.Compatibility +android.graphics.ComposePathEffect +android.graphics.ComposeShader +android.graphics.CornerPathEffect +android.graphics.DashPathEffect +android.graphics.DiscretePathEffect +android.graphics.DrawFilter +android.graphics.EmbossMaskFilter +android.graphics.FontFamily +android.graphics.FontListParser +android.graphics.ForceDarkType +android.graphics.FrameInfo +android.graphics.Gainmap +android.graphics.GraphicBuffer +android.graphics.GraphicsProtos +android.graphics.GraphicsStatsService +android.graphics.HardwareBufferRenderer +android.graphics.HardwareRenderer +android.graphics.HardwareRendererObserver +android.graphics.ImageDecoder +android.graphics.ImageFormat +android.graphics.LayerRasterizer +android.graphics.LeakyTypefaceStorage +android.graphics.LightingColorFilter +android.graphics.LinearGradient +android.graphics.MaskFilter +android.graphics.Mesh +android.graphics.MeshSpecification +android.graphics.Movie +android.graphics.NinePatch +android.graphics.Paint +android.graphics.PaintFlagsDrawFilter +android.graphics.PathDashPathEffect +android.graphics.PathEffect +android.graphics.PathIterator +android.graphics.PathMeasure +android.graphics.Picture +android.graphics.PixelXorXfermode +android.graphics.PorterDuff +android.graphics.PorterDuffColorFilter +android.graphics.PorterDuffXfermode +android.graphics.PostProcessor +android.graphics.RadialGradient +android.graphics.Rasterizer +android.graphics.RecordingCanvas +android.graphics.Region +android.graphics.RegionIterator +android.graphics.RenderEffect +android.graphics.RenderNode +android.graphics.RuntimeColorFilter +android.graphics.RuntimeShader +android.graphics.RuntimeXfermode +android.graphics.Shader +android.graphics.SumPathEffect +android.graphics.SurfaceTexture +android.graphics.SweepGradient +android.graphics.TableMaskFilter +android.graphics.TemporaryBuffer +android.graphics.TextureLayer +android.graphics.Typeface +android.graphics.Xfermode +android.graphics.YuvImage +android.graphics.fonts.Font +android.graphics.fonts.FontCustomizationParser +android.graphics.fonts.FontFamily +android.graphics.fonts.FontFamilyUpdateRequest +android.graphics.fonts.FontFileUpdateRequest +android.graphics.fonts.FontFileUtil +android.graphics.fonts.FontStyle +android.graphics.fonts.FontVariationAxis +android.graphics.text.GraphemeBreak +android.graphics.text.LineBreaker +android.graphics.text.MeasuredText +android.graphics.text.PositionedGlyphs +android.graphics.text.TextRunShaper +android.text.AlteredCharSequence +android.text.AndroidBidi +android.text.AndroidCharacter +android.text.Annotation +android.text.AutoGrowArray +android.text.AutoText +android.text.BidiFormatter +android.text.BoringLayout +android.text.CharSequenceCharacterIterator +android.text.ClipboardManager +android.text.DynamicLayout +android.text.Editable +android.text.Emoji +android.text.EmojiConsistency +android.text.FontConfig +android.text.GetChars +android.text.GraphemeClusterSegmentFinder +android.text.GraphicsOperations +android.text.Highlights +android.text.Hyphenator +android.text.InputFilter +android.text.InputType +android.text.Layout +android.text.LoginFilter +android.text.MeasuredParagraph +android.text.NoCopySpan +android.text.PackedIntVector +android.text.PackedObjectVector +android.text.ParcelableSpan +android.text.PrecomputedText +android.text.SegmentFinder +android.text.Selection +android.text.SpanColors +android.text.SpanSet +android.text.SpanWatcher +android.text.Spannable +android.text.SpannableString +android.text.SpannableStringBuilder +android.text.SpannableStringInternal +android.text.Spanned +android.text.SpannedString +android.text.StaticLayout +android.text.TextDirectionHeuristic +android.text.TextDirectionHeuristics +android.text.TextLine +android.text.TextPaint +android.text.TextShaper +android.text.TextWatcher +android.text.WordSegmentFinder +android.text.format.DateFormat +android.text.format.DateIntervalFormat +android.text.format.DateTimeFormat +android.text.format.DateUtils +android.text.format.DateUtilsBridge +android.text.format.Formatter +android.text.format.RelativeDateTimeFormatter +android.text.format.Time +android.text.format.TimeFormatter +android.text.format.TimeMigrationUtils +android.text.method.AllCapsTransformationMethod +android.text.method.ArrowKeyMovementMethod +android.text.method.BaseKeyListener +android.text.method.BaseMovementMethod +android.text.method.CharacterPickerDialog +android.text.method.DateKeyListener +android.text.method.DateTimeKeyListener +android.text.method.DialerKeyListener +android.text.method.DigitsKeyListener +android.text.method.HideReturnsTransformationMethod +android.text.method.InsertModeTransformationMethod +android.text.method.KeyListener +android.text.method.LinkMovementMethod +android.text.method.MetaKeyKeyListener +android.text.method.MovementMethod +android.text.method.MultiTapKeyListener +android.text.method.NumberKeyListener +android.text.method.OffsetMapping +android.text.method.PasswordTransformationMethod +android.text.method.QwertyKeyListener +android.text.method.ReplacementTransformationMethod +android.text.method.ScrollingMovementMethod +android.text.method.SingleLineTransformationMethod +android.text.method.TextKeyListener +android.text.method.TimeKeyListener +android.text.method.Touch +android.text.method.TransformationMethod +android.text.method.TransformationMethod2 +android.text.method.TranslationTransformationMethod +android.text.method.WordIterator +android.text.style.AbsoluteSizeSpan +android.text.style.AccessibilityClickableSpan +android.text.style.AccessibilityReplacementSpan +android.text.style.AccessibilityURLSpan +android.text.style.AlignmentSpan +android.text.style.BackgroundColorSpan +android.text.style.BulletSpan +android.text.style.CharacterStyle +android.text.style.ClickableSpan +android.text.style.ForegroundColorSpan +android.text.style.IconMarginSpan +android.text.style.LeadingMarginSpan +android.text.style.LineBackgroundSpan +android.text.style.LineBreakConfigSpan +android.text.style.LineHeightSpan +android.text.style.LocaleSpan +android.text.style.MaskFilterSpan +android.text.style.MetricAffectingSpan +android.text.style.NoWritingToolsSpan +android.text.style.ParagraphStyle +android.text.style.QuoteSpan +android.text.style.RasterizerSpan +android.text.style.RelativeSizeSpan +android.text.style.ReplacementSpan +android.text.style.ScaleXSpan +android.text.style.SpanUtils +android.text.style.SpellCheckSpan +android.text.style.StrikethroughSpan +android.text.style.StyleSpan +android.text.style.SubscriptSpan +android.text.style.SuggestionRangeSpan +android.text.style.SuggestionSpan +android.text.style.SuperscriptSpan +android.text.style.TabStopSpan +android.text.style.TextAppearanceSpan +android.text.style.TtsSpan +android.text.style.TypefaceSpan +android.text.style.URLSpan +android.text.style.UnderlineSpan +android.text.style.UpdateAppearance +android.text.style.UpdateLayout +android.text.style.WrapTogetherSpan +android.text.util.Rfc822Token +android.text.util.Rfc822Tokenizer diff --git a/ravenwood/texts/ravenwood-build.prop b/ravenwood/texts/ravenwood-build.prop index 7cc4454a6e56..37c50f11f73f 100644 --- a/ravenwood/texts/ravenwood-build.prop +++ b/ravenwood/texts/ravenwood-build.prop @@ -8,7 +8,11 @@ ro.soc.manufacturer=Android ro.soc.model=Ravenwood ro.debuggable=1 -# The ones starting with "ro.product" or "ro.bild" will be copied to all "partitions" too. +# For the graphics stack +ro.hwui.max_texture_allocation_size=104857600 +persist.sys.locale=en-US + +# The ones starting with "ro.product" or "ro.build" will be copied to all "partitions" too. # See RavenwoodSystemProperties. ro.product.brand=Android ro.product.device=Ravenwood diff --git a/ravenwood/texts/ravenwood-common-policies.txt b/ravenwood/texts/ravenwood-common-policies.txt index fd4ea6cf40c2..f0f4b8580f7d 100644 --- a/ravenwood/texts/ravenwood-common-policies.txt +++ b/ravenwood/texts/ravenwood-common-policies.txt @@ -21,3 +21,7 @@ class java.io.FileDescriptor # no-pta method setInt$ @com.android.ravenwood.RavenwoodJdkPatch.setInt$ class java.util.LinkedHashMap # no-pta method eldest @com.android.ravenwood.RavenwoodJdkPatch.eldest + +# Always set flag UNICODE_CHARACTER_CLASS when compiling regex +class java.util.regex.Pattern keep + method compile @com.android.ravenwood.RavenwoodJdkPatch.compilePattern diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt index fff9e6ad41d5..0695316543ae 100644 --- a/ravenwood/texts/ravenwood-framework-policies.txt +++ b/ravenwood/texts/ravenwood-framework-policies.txt @@ -63,3 +63,22 @@ class android.text.ClipboardManager keep # no-pta # Just enough to allow ResourcesManager to run class android.hardware.display.DisplayManagerGlobal keep # no-pta method getInstance ()Landroid/hardware/display/DisplayManagerGlobal; ignore # no-pta + +# Bare minimum to support running ImageDecoderTest +class android.graphics.drawable.Drawable$ConstantState keepclass # no-pta +class android.graphics.drawable.BitmapDrawable$BitmapState keepclass # no-pta +class android.graphics.drawable.BitmapDrawable keep # no-pta + method <init> (Landroid/content/res/Resources;Landroid/graphics/Bitmap;)V keep + method init * keep + method updateLocalState * keep + method computeBitmapSize * keep + method getIntrinsicWidth * keep + method getIntrinsicHeight * keep + method getBitmap * keep +class android.graphics.drawable.Drawable keep # no-pta + method <init> ()V keep + method resolveDensity * keep + method updateBlendModeFilter * ignore + +class android.os.StrictMode keep # no-pta + method noteSlowCall (Ljava/lang/String;)V ignore diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 5e1fe8a60973..b52b3dabd47d 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -145,16 +145,6 @@ flag { } flag { - name: "enable_magnification_follows_mouse_bugfix" - namespace: "accessibility" - description: "Whether to enable mouse following for fullscreen magnification" - bug: "354696546" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "enable_magnification_follows_mouse_with_pointer_motion_filter" namespace: "accessibility" description: "Whether to enable mouse following using pointer motion filter" diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 9b5f22afb81d..4e41808626b4 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -53,7 +53,6 @@ import com.android.server.accessibility.magnification.FullScreenMagnificationGes import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper; import com.android.server.accessibility.magnification.MagnificationGestureHandler; import com.android.server.accessibility.magnification.MagnificationKeyHandler; -import com.android.server.accessibility.magnification.MouseEventHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationPromptController; import com.android.server.policy.WindowManagerPolicy; @@ -899,8 +898,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo triggerable, new WindowMagnificationPromptController(displayContext, mUserId), displayId, - fullScreenMagnificationVibrationHelper, - new MouseEventHandler(controller)); + fullScreenMagnificationVibrationHelper); } return magnificationGestureHandler; } diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index 4fa0d506f09e..aa82df493f84 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -101,8 +101,15 @@ public class AutoclickController extends BaseEventStreamTransformation { } @Override - public void toggleAutoclickPause() { - // TODO(b/388872274): allows users to pause the autoclick. + public void toggleAutoclickPause(boolean paused) { + if (paused) { + if (mClickScheduler != null) { + mClickScheduler.cancel(); + } + if (mAutoclickIndicatorScheduler != null) { + mAutoclickIndicatorScheduler.cancel(); + } + } } }; @@ -133,7 +140,9 @@ public class AutoclickController extends BaseEventStreamTransformation { mAutoclickIndicatorScheduler); } - handleMouseMotion(event, policyFlags); + if (!isPaused()) { + handleMouseMotion(event, policyFlags); + } } else if (mClickScheduler != null) { mClickScheduler.cancel(); } @@ -216,6 +225,11 @@ public class AutoclickController extends BaseEventStreamTransformation { } } + private boolean isPaused() { + // TODO (b/397460424): Unpause when hovering over panel. + return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused(); + } + /** * Observes autoclick setting values, and updates ClickScheduler delay and indicator size * whenever the setting value changes. diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java index 01f359fc5753..6beb47acb8b1 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java @@ -123,6 +123,7 @@ public class AutoclickIndicatorView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Get the screen dimensions. + // TODO(b/397944891): Handle device rotation case. DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int screenWidth = displayMetrics.widthPixels; int screenHeight = displayMetrics.heightPixels; diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java index 23c5cc4111f6..342675a65360 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java @@ -25,6 +25,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; @@ -51,6 +52,16 @@ public class AutoclickTypePanel { public static final int CORNER_TOP_LEFT = 2; public static final int CORNER_TOP_RIGHT = 3; + // Distance between panel and screen edge. + // TODO(b/396402941): Finalize edge margin. + private static final int PANEL_EDGE_MARGIN = 15; + + // Touch point when drag starts, it can be anywhere inside the panel. + private float mTouchStartX, mTouchStartY; + // Initial panel position in screen coordinates. + private int mPanelStartX, mPanelStartY; + private boolean mIsDragging = false; + // Types of click the AutoclickTypePanel supports. @IntDef({ AUTOCLICK_TYPE_LEFT_CLICK, @@ -79,11 +90,20 @@ public class AutoclickTypePanel { // An interface exposed to {@link AutoclickController) to handle different actions on the panel, // including changing autoclick type, pausing/resuming autoclick. public interface ClickPanelControllerInterface { - // Allows users to change a different autoclick type. + /** + * Allows users to change a different autoclick type. + * + * @param clickType The new autoclick type to use. Should be one of the values defined in + * {@link AutoclickType}. + */ void handleAutoclickTypeChange(@AutoclickType int clickType); - // Allows users to pause/resume the autoclick. - void toggleAutoclickPause(); + /** + * Allows users to pause or resume autoclick. + * + * @param paused {@code true} to pause autoclick, {@code false} to resume. + */ + void toggleAutoclickPause(boolean paused); } private final Context mContext; @@ -92,6 +112,8 @@ public class AutoclickTypePanel { private final WindowManager mWindowManager; + private WindowManager.LayoutParams mParams; + private final ClickPanelControllerInterface mClickPanelController; // Whether the panel is expanded or not. @@ -124,6 +146,7 @@ public class AutoclickTypePanel { mContext = context; mWindowManager = windowManager; mClickPanelController = clickPanelController; + mParams = getDefaultLayoutParams(); mPauseButtonDrawable = mContext.getDrawable( R.drawable.accessibility_autoclick_pause); @@ -145,6 +168,91 @@ public class AutoclickTypePanel { mPositionButton = mContentView.findViewById(R.id.accessibility_autoclick_position_layout); initializeButtonState(); + + // Set up touch event handling for the panel to allow the user to drag and reposition the + // panel by touching and moving it. + mContentView.setOnTouchListener(this::onPanelTouch); + } + + /** + * Handles touch events on the panel, enabling the user to drag and reposition it. + * This function supports the draggable panel feature, allowing users to move the panel + * to different screen locations for better usability and customization. + */ + private boolean onPanelTouch(View v, MotionEvent event) { + // TODO(b/397681794): Make sure this works on multiple screens. + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // Store initial touch positions. + mTouchStartX = event.getRawX(); + mTouchStartY = event.getRawY(); + + // Store initial panel position relative to screen's top-left corner. + // getLocationOnScreen provides coordinates relative to the top-left corner of the + // screen's display. We are using this coordinate system to consistently track the + // panel's position during drag operations. + int[] location = new int[2]; + v.getLocationOnScreen(location); + mPanelStartX = location[0]; + mPanelStartY = location[1]; + return true; + case MotionEvent.ACTION_MOVE: + mIsDragging = true; + + // Set panel gravity to TOP|LEFT to match getLocationOnScreen's coordinate system + mParams.gravity = Gravity.LEFT | Gravity.TOP; + + if (mIsDragging) { + // Calculate touch distance moved from start position. + float deltaX = event.getRawX() - mTouchStartX; + float deltaY = event.getRawY() - mTouchStartY; + + // Update panel position, based on Top-Left absolute positioning. + mParams.x = mPanelStartX + (int) deltaX; + mParams.y = mPanelStartY + (int) deltaY; + mWindowManager.updateViewLayout(mContentView, mParams); + } + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mIsDragging) { + // When drag ends, snap panel to nearest edge. + snapToNearestEdge(mParams); + } + mIsDragging = false; + return true; + } + return false; + } + + private void snapToNearestEdge(WindowManager.LayoutParams params) { + // Get screen width to determine which side to snap to. + // TODO(b/397944891): Handle device rotation case. + int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels; + int yPosition = params.y; + + // Determine which half of the screen the panel is on. + boolean isOnLeftHalf = params.x < screenWidth / 2; + + if (isOnLeftHalf) { + // Snap to left edge. Set params.gravity to make sure x, y offsets from correct anchor. + params.gravity = Gravity.START | Gravity.TOP; + // Set the current corner to be bottom-left to ensure that the subsequent reposition + // action rotates the panel clockwise from bottom-left towards top-left. + mCurrentCornerIndex = 1; + } else { + // Snap to right edge. Set params.gravity to make sure x, y offsets from correct anchor. + params.gravity = Gravity.END | Gravity.TOP; + // Set the current corner to be top-right to ensure that the subsequent reposition + // action rotates the panel clockwise from top-right towards bottom-right. + mCurrentCornerIndex = 3; + } + + // Apply final position: set params.x to be edge margin, params.y to maintain vertical + // position. + params.x = PANEL_EDGE_MARGIN; + params.y = yPosition; + mWindowManager.updateViewLayout(mContentView, params); } private void initializeButtonState() { @@ -200,7 +308,7 @@ public class AutoclickTypePanel { } public void show() { - mWindowManager.addView(mContentView, getLayoutParams()); + mWindowManager.addView(mContentView, mParams); } public void hide() { @@ -211,6 +319,10 @@ public class AutoclickTypePanel { mWindowManager.removeView(mContentView); } + public boolean isPaused() { + return mPaused; + } + /** Toggles the panel expanded or collapsed state. */ private void togglePanelExpansion(@AutoclickType int clickType) { final LinearLayout button = getButtonFromClickType(clickType); @@ -234,6 +346,7 @@ public class AutoclickTypePanel { private void togglePause() { mPaused = !mPaused; + mClickPanelController.toggleAutoclickPause(mPaused); ImageButton imageButton = (ImageButton) mPauseButton.getChildAt(/* index= */ 0); if (mPaused) { @@ -277,9 +390,8 @@ public class AutoclickTypePanel { @Corner int nextCornerIndex = (mCurrentCornerIndex + 1) % CORNER_ROTATION_ORDER.length; mCurrentCornerIndex = nextCornerIndex; - // getLayoutParams() will update the panel position based on current corner. - WindowManager.LayoutParams params = getLayoutParams(); - mWindowManager.updateViewLayout(mContentView, params); + setPanelPositionForCorner(mParams, mCurrentCornerIndex); + mWindowManager.updateViewLayout(mContentView, mParams); } private void setPanelPositionForCorner(WindowManager.LayoutParams params, @Corner int corner) { @@ -289,22 +401,22 @@ public class AutoclickTypePanel { switch (corner) { case CORNER_BOTTOM_RIGHT: params.gravity = Gravity.END | Gravity.BOTTOM; - params.x = 15; + params.x = PANEL_EDGE_MARGIN; params.y = 90; break; case CORNER_BOTTOM_LEFT: params.gravity = Gravity.START | Gravity.BOTTOM; - params.x = 15; + params.x = PANEL_EDGE_MARGIN; params.y = 90; break; case CORNER_TOP_LEFT: params.gravity = Gravity.START | Gravity.TOP; - params.x = 15; + params.x = PANEL_EDGE_MARGIN; params.y = 30; break; case CORNER_TOP_RIGHT: params.gravity = Gravity.END | Gravity.TOP; - params.x = 15; + params.x = PANEL_EDGE_MARGIN; params.y = 30; break; default: @@ -329,13 +441,22 @@ public class AutoclickTypePanel { return mCurrentCornerIndex; } + @VisibleForTesting + WindowManager.LayoutParams getLayoutParamsForTesting() { + return mParams; + } + + @VisibleForTesting + boolean getIsDraggingForTesting() { + return mIsDragging; + } + /** * Retrieves the layout params for AutoclickIndicatorView, used when it's added to the Window * Manager. */ - @VisibleForTesting @NonNull - WindowManager.LayoutParams getLayoutParams() { + private WindowManager.LayoutParams getDefaultLayoutParams() { final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; @@ -348,7 +469,7 @@ public class AutoclickTypePanel { mContext.getString(R.string.accessibility_autoclick_type_settings_panel_title); layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; - setPanelPositionForCorner(layoutParams, mCurrentCornerIndex); + setPanelPositionForCorner(layoutParams, CORNER_BOTTOM_RIGHT); return layoutParams; } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index d11ae0a6ad97..e0dd8b601a3d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -182,7 +182,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private final int mMinimumVelocity; private final int mMaximumVelocity; - private MouseEventHandler mMouseEventHandler; + private final MouseEventHandler mMouseEventHandler; public FullScreenMagnificationGestureHandler( @UiContext Context context, @@ -194,8 +194,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH boolean detectShortcutTrigger, @NonNull WindowMagnificationPromptController promptController, int displayId, - FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper, - MouseEventHandler mouseEventHandler) { + FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) { this( context, fullScreenMagnificationController, @@ -210,8 +209,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH /* magnificationLogger= */ null, ViewConfiguration.get(context), new OneFingerPanningSettingsProvider( - context, Flags.enableMagnificationOneFingerPanningGesture()), - mouseEventHandler); + context, Flags.enableMagnificationOneFingerPanningGesture())); } /** Constructor for tests. */ @@ -229,8 +227,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper, MagnificationLogger magnificationLogger, ViewConfiguration viewConfiguration, - OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider, - MouseEventHandler mouseEventHandler) { + OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider) { super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap, detectShortcutTrigger, trace, callback); if (DEBUG_ALL) { @@ -316,7 +313,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mOverscrollEdgeSlop = context.getResources().getDimensionPixelSize( R.dimen.accessibility_fullscreen_magnification_gesture_edge_slop); mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); - mMouseEventHandler = mouseEventHandler; + mMouseEventHandler = new MouseEventHandler(mFullScreenMagnificationController); if (mDetectShortcutTrigger) { mScreenStateReceiver = new ScreenStateReceiver(context, this); @@ -340,15 +337,14 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @Override void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (Flags.enableMagnificationFollowsMouseBugfix()) { - if (mFullScreenMagnificationController.isActivated(mDisplayId)) { - // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are - // over, rather than only interacting with the current display. - - // Send through the mouse/stylus event handler. - mMouseEventHandler.onEvent(event, mDisplayId); - } + if (!mFullScreenMagnificationController.isActivated(mDisplayId)) { + return; } + // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are + // over, rather than only interacting with the current display. + + // Send through the mouse/stylus event handler. + mMouseEventHandler.onEvent(event, mDisplayId); } private void handleTouchEventWith( @@ -1170,8 +1166,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (Flags.enableMagnificationFollowsMouseBugfix() - && !event.isFromSource(SOURCE_TOUCHSCREEN)) { + if (!event.isFromSource(SOURCE_TOUCHSCREEN)) { // Only touch events need to be cached and sent later. return; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index fa86ba39bb1a..6b39c98887bd 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -146,8 +146,7 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo } break; case SOURCE_MOUSE: case SOURCE_STYLUS: { - if (magnificationShortcutExists() - && Flags.enableMagnificationFollowsMouseBugfix()) { + if (magnificationShortcutExists()) { handleMouseOrStylusEvent(event, rawEvent, policyFlags); } } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 74a87ed92f52..4441db78e4b5 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -5232,7 +5232,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } return singleCategoryKeyedEntries; - } catch (IOException e) { + } catch (Exception e) { Slog.e(TAG, "Failed to load generated previews for " + provider, e); return new SparseArray<>(); } @@ -5261,7 +5261,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku try { provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto( input); - } catch (IOException e) { + } catch (Exception e) { Slog.e(TAG, "Failed to read generated previews from file for " + provider, e); previewsFile.delete(); provider.info.generatedPreviewCategories = 0; @@ -5314,7 +5314,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId()); } } - } catch (IOException e) { + } catch (Exception e) { if (file != null && stream != null) { file.failWrite(stream); } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index d2a5734f323f..b6fe0ad37078 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -49,7 +49,6 @@ import static com.android.internal.util.XmlUtils.writeStringAttribute; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; -import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -2443,6 +2442,7 @@ class StorageManagerService extends IStorageManager.Stub } catch (Installer.InstallerException e) { Slog.e(TAG, "Failed unmount mirror data", e); } + extendWatchdogTimeout("#unmount might be slow"); mVold.unmount(vol.getId()); mStorageSessionController.onVolumeUnmount(vol.getImmutableVolumeInfo()); } catch (Exception e) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 07a4d52f56ec..8b701f0e2069 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -198,6 +198,7 @@ import android.annotation.Nullable; import android.annotation.PermissionMethod; import android.annotation.PermissionName; import android.annotation.RequiresPermission; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityClient; @@ -3921,8 +3922,8 @@ public class ActivityManagerService extends IActivityManager.Stub * The pkg name and app id have to be specified. */ @Override - public void killApplication(String pkg, int appId, int userId, String reason, - int exitInfoReason) { + public void killApplication(String pkg, int appId, @CanBeALL @UserIdInt int userId, + String reason, int exitInfoReason) { if (pkg == null) { return; } @@ -4307,7 +4308,7 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean forceStopPackageLocked(String packageName, int appId, boolean callerWillRestart, boolean purgeCache, boolean doit, boolean evenPersistent, boolean uninstalling, boolean packageStateStopped, - int userId, String reasonString, int reason) { + @CanBeALL @UserIdInt int userId, String reasonString, int reason) { return forceStopPackageInternalLocked(packageName, appId, callerWillRestart, purgeCache, doit, evenPersistent, uninstalling, packageStateStopped, userId, reasonString, reason, ProcessList.INVALID_ADJ); @@ -4317,7 +4318,7 @@ public class ActivityManagerService extends IActivityManager.Stub private boolean forceStopPackageInternalLocked(String packageName, int appId, boolean callerWillRestart, boolean purgeCache, boolean doit, boolean evenPersistent, boolean uninstalling, boolean packageStateStopped, - int userId, String reasonString, int reason, int minOomAdj) { + @CanBeALL @UserIdInt int userId, String reasonString, int reason, int minOomAdj) { int i; if (userId == UserHandle.USER_ALL && packageName == null) { @@ -18093,7 +18094,7 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void killApplicationSync(String pkgName, int appId, int userId, + public void killApplicationSync(String pkgName, int appId, @CanBeALL @UserIdInt int userId, String reason, int exitInfoReason) { if (pkgName == null) { return; diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 61c5501a7b5a..13d367a95942 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -446,6 +446,8 @@ public class OomAdjuster { private static final int CACHING_UI_SERVICE_CLIENT_ADJ_THRESHOLD = Flags.raiseBoundUiServiceThreshold() ? SERVICE_ADJ : PERCEPTIBLE_APP_ADJ; + static final long PERCEPTIBLE_TASK_TIMEOUT_MILLIS = 5 * 60 * 1000; + @VisibleForTesting public static class Injector { boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId, @@ -1847,7 +1849,7 @@ public class OomAdjuster { mHasVisibleActivities = false; } - void onOtherActivity() { + void onOtherActivity(long perceptibleTaskStoppedTimeMillis) { if (procState > PROCESS_STATE_CACHED_ACTIVITY) { procState = PROCESS_STATE_CACHED_ACTIVITY; mAdjType = "cch-act"; @@ -1856,6 +1858,28 @@ public class OomAdjuster { "Raise procstate to cached activity: " + app); } } + if (Flags.perceptibleTasks() && adj > PERCEPTIBLE_MEDIUM_APP_ADJ) { + if (perceptibleTaskStoppedTimeMillis >= 0) { + final long now = mInjector.getUptimeMillis(); + if (now - perceptibleTaskStoppedTimeMillis < PERCEPTIBLE_TASK_TIMEOUT_MILLIS) { + adj = PERCEPTIBLE_MEDIUM_APP_ADJ; + mAdjType = "perceptible-act"; + if (procState > PROCESS_STATE_IMPORTANT_BACKGROUND) { + procState = PROCESS_STATE_IMPORTANT_BACKGROUND; + } + + maybeSetProcessFollowUpUpdateLocked(app, + perceptibleTaskStoppedTimeMillis + PERCEPTIBLE_TASK_TIMEOUT_MILLIS, + now); + } else if (adj > PREVIOUS_APP_ADJ) { + adj = PREVIOUS_APP_ADJ; + mAdjType = "stale-perceptible-act"; + if (procState > PROCESS_STATE_LAST_ACTIVITY) { + procState = PROCESS_STATE_LAST_ACTIVITY; + } + } + } + } mHasVisibleActivities = false; } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b8babe69d5a7..a61368c4bc36 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -67,6 +67,8 @@ import static com.android.server.wm.WindowProcessController.STOPPED_STATE_FORCE_ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManager.ProcessCapability; import android.app.ActivityThread; @@ -2961,8 +2963,8 @@ public final class ProcessList { } @GuardedBy({"mService", "mProcLock"}) - boolean killPackageProcessesLSP(String packageName, int appId, int userId, int minOomAdj, - int reasonCode, int subReason, String reason) { + boolean killPackageProcessesLSP(String packageName, int appId, @CanBeALL @UserIdInt int userId, + int minOomAdj, int reasonCode, int subReason, String reason) { return killPackageProcessesLSP(packageName, appId, userId, minOomAdj, false /* callerWillRestart */, true /* allowRestart */, true /* doit */, false /* evenPersistent */, false /* setRemoved */, false /* uninstalling */, @@ -2970,7 +2972,8 @@ public final class ProcessList { } @GuardedBy("mService") - void killAppZygotesLocked(String packageName, int appId, int userId, boolean force) { + void killAppZygotesLocked(String packageName, int appId, @CanBeALL @UserIdInt int userId, + boolean force) { // See if there are any app zygotes running for this packageName / UID combination, // and kill it if so. final ArrayList<AppZygote> zygotesToKill = new ArrayList<>(); @@ -3050,9 +3053,9 @@ public final class ProcessList { @GuardedBy({"mService", "mProcLock"}) boolean killPackageProcessesLSP(String packageName, int appId, - int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, - boolean doit, boolean evenPersistent, boolean setRemoved, boolean uninstalling, - int reasonCode, int subReason, String reason) { + @CanBeALL @UserIdInt int userId, int minOomAdj, boolean callerWillRestart, + boolean allowRestart, boolean doit, boolean evenPersistent, boolean setRemoved, + boolean uninstalling, int reasonCode, int subReason, String reason) { final PackageManagerInternal pm = mService.getPackageManagerInternal(); final ArrayList<Pair<ProcessRecord, Boolean>> procs = new ArrayList<>(); @@ -5220,7 +5223,7 @@ public final class ProcessList { } @GuardedBy("mService") - void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) { + void sendPackageBroadcastLocked(int cmd, String[] packages, @CanBeALL @UserIdInt int userId) { boolean foundProcess = false; for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord r = mLruProcesses.get(i); diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index b0f808b39053..25175e6bee5f 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -1120,7 +1120,8 @@ final class ProcessStateRecord { } else if ((flags & ACTIVITY_STATE_FLAG_IS_STOPPING) != 0) { callback.onStoppingActivity((flags & ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) != 0); } else { - callback.onOtherActivity(); + final long ts = mApp.getWindowProcessController().getPerceptibleTaskStoppedTimeMillis(); + callback.onOtherActivity(ts); } mCachedAdj = callback.adj; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index e0fbaf43ea43..18f3500b2d56 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -4059,7 +4059,7 @@ class UserController implements Handler.Callback { synchronized (mUserSwitchingDialogLock) { dismissUserSwitchingDialog(null); mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser, - switchingFromSystemUserMessage, switchingToSystemUserMessage); + mHandler, switchingFromSystemUserMessage, switchingToSystemUserMessage); mUserSwitchingDialog.show(onShown); } } diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index d1fcb9d1ca37..223e0b79ec0b 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -34,7 +34,6 @@ import android.graphics.drawable.Animatable2; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.os.Looper; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; @@ -72,7 +71,7 @@ class UserSwitchingDialog extends Dialog { // Time to wait for the onAnimationEnd() callbacks before moving on private static final int ANIMATION_TIMEOUT_MS = 1000; - private final Handler mHandler = new Handler(Looper.myLooper()); + private final Handler mHandler; protected final UserInfo mOldUser; protected final UserInfo mNewUser; @@ -81,13 +80,14 @@ class UserSwitchingDialog extends Dialog { protected final Context mContext; private final int mTraceCookie; - UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, + UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, Handler handler, String switchingFromSystemUserMessage, String switchingToSystemUserMessage) { super(context, R.style.Theme_Material_NoActionBar_Fullscreen); mContext = context; mOldUser = oldUser; mNewUser = newUser; + mHandler = handler; mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage; mSwitchingToSystemUserMessage = switchingToSystemUserMessage; mDisableAnimations = SystemProperties.getBoolean( diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 27c384a22fb6..c8fedf3d1765 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -293,6 +293,13 @@ flag { } flag { + name: "perceptible_tasks" + namespace: "system_performance" + description: "Boost the oom_score_adj of activities in perceptible tasks" + bug: "370890207" +} + +flag { name: "expedite_activity_launch_on_cold_start" namespace: "system_performance" description: "Notify ActivityTaskManager of cold starts early to fix app launch behavior." diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index d800503c658e..b85967fa2bac 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -954,7 +954,8 @@ public class AudioService extends IAudioService.Stub /** * Stores information about a device using absolute volume behavior. */ - private static final class AbsoluteVolumeDeviceInfo { + private static final class AbsoluteVolumeDeviceInfo implements IBinder.DeathRecipient { + private final AudioService mParent; private final AudioDeviceAttributes mDevice; private final List<VolumeInfo> mVolumeInfos; private final IAudioDeviceVolumeDispatcher mCallback; @@ -962,16 +963,26 @@ public class AudioService extends IAudioService.Stub private @AudioManager.AbsoluteDeviceVolumeBehavior int mDeviceVolumeBehavior; private AbsoluteVolumeDeviceInfo( + AudioService parent, AudioDeviceAttributes device, List<VolumeInfo> volumeInfos, IAudioDeviceVolumeDispatcher callback, boolean handlesVolumeAdjustment, @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) { + this.mParent = parent; this.mDevice = device; this.mVolumeInfos = volumeInfos; this.mCallback = callback; this.mHandlesVolumeAdjustment = handlesVolumeAdjustment; this.mDeviceVolumeBehavior = behavior; + + try { + this.mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException | NullPointerException e) { + // NPE can be raised when mocking the callback object + Slog.w(TAG, "Exception: " + e + + "\nCannot listen to callback binder death for device " + mDevice); + } } /** @@ -991,6 +1002,25 @@ public class AudioService extends IAudioService.Stub } return null; } + + @Override + public void binderDied() { + if (mParent.removeAudioSystemDeviceOutFromAbsVolumeDevices(mDevice.getInternalType()) + != null) { + mParent.dispatchDeviceVolumeBehavior(mDevice, + AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE); + } + } + + public void unlinkToDeath() { + try { + mCallback.asBinder().unlinkToDeath(this, 0); + } catch (NullPointerException e) { + // NPE can be raised when mocking the callback object + Slog.w(TAG, "Exception: " + e + + "\nCannot unlink to death, null binder object for device " + mDevice); + } + } } // Devices for the which use the "absolute volume" concept (framework sends audio signal @@ -8142,16 +8172,16 @@ public class AudioService extends IAudioService.Stub int deviceOut = device.getInternalType(); if (register) { - AbsoluteVolumeDeviceInfo info = new AbsoluteVolumeDeviceInfo( + AbsoluteVolumeDeviceInfo info = new AbsoluteVolumeDeviceInfo(this, device, volumes, cb, handlesVolumeAdjustment, deviceVolumeBehavior); final AbsoluteVolumeDeviceInfo oldInfo = getAbsoluteVolumeDeviceInfo(deviceOut); + addAudioSystemDeviceOutToAbsVolumeDevices(deviceOut, info); boolean volumeBehaviorChanged = (oldInfo == null) || (oldInfo.mDeviceVolumeBehavior != deviceVolumeBehavior); if (volumeBehaviorChanged) { removeAudioSystemDeviceOutFromFullVolumeDevices(deviceOut); removeAudioSystemDeviceOutFromFixedVolumeDevices(deviceOut); - addAudioSystemDeviceOutToAbsVolumeDevices(deviceOut, info); dispatchDeviceVolumeBehavior(device, deviceVolumeBehavior); } @@ -8177,8 +8207,10 @@ public class AudioService extends IAudioService.Stub } } } else { - boolean wasAbsVol = removeAudioSystemDeviceOutFromAbsVolumeDevices(deviceOut) != null; - if (wasAbsVol) { + AbsoluteVolumeDeviceInfo deviceInfo = removeAudioSystemDeviceOutFromAbsVolumeDevices( + deviceOut); + if (deviceInfo != null) { + deviceInfo.unlinkToDeath(); dispatchDeviceVolumeBehavior(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE); } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index a5058dd51a33..cf5fa9699ca9 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -540,9 +540,23 @@ public class BiometricService extends SystemService { DEFAULT_MANDATORY_BIOMETRICS_STATUS) && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId, DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS) - && getEnabledForApps(userId, TYPE_ANY_BIOMETRIC) - && (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */) - || mFaceEnrolledForUser.getOrDefault(userId, false /* default */)); + && getBiometricStatusForIdentityCheck(userId); + } + + private boolean getBiometricStatusForIdentityCheck(int userId) { + if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) { + if (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */) + && getEnabledForApps(userId, TYPE_FINGERPRINT)) { + return true; + } else { + return mFaceEnrolledForUser.getOrDefault(userId, false /* default */) + && getEnabledForApps(userId, TYPE_FACE); + } + } else { + return (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */) + || mFaceEnrolledForUser.getOrDefault(userId, false /* default */)) + && getEnabledForApps(userId, TYPE_ANY_BIOMETRIC); + } } void notifyEnabledOnKeyguardCallbacks(int userId, int modality) { diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index c393e921d957..79af6ed9d60b 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -24,6 +24,8 @@ import android.accounts.AccountManagerInternal; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SpecialUsers.CanBeALL; +import android.annotation.SpecialUsers.CanBeCURRENT; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManager.RestrictionLevel; @@ -361,7 +363,8 @@ public final class ContentService extends IContentService.Stub { */ @Override public void registerContentObserver(Uri uri, boolean notifyForDescendants, - IContentObserver observer, int userHandle, int targetSdkVersion) { + IContentObserver observer, @CanBeALL @CanBeCURRENT @UserIdInt int userHandle, + int targetSdkVersion) { if (observer == null || uri == null) { throw new IllegalArgumentException("You must pass a valid uri and observer"); } @@ -1398,8 +1401,8 @@ public final class ContentService extends IContentService.Stub { } } - private int handleIncomingUser(Uri uri, int pid, int uid, int modeFlags, boolean allowNonFull, - int userId) { + private @CanBeALL @UserIdInt int handleIncomingUser(Uri uri, int pid, int uid, int modeFlags, + boolean allowNonFull, @CanBeALL @CanBeCURRENT @UserIdInt int userId) { if (userId == UserHandle.USER_CURRENT) { userId = ActivityManager.getCurrentUser(); } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 872f33484951..f4daf8761e9b 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -890,10 +890,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // still need to let WindowManager know so it can update its own internal state for // things like display cutouts. display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo); - if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) { - logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED - | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED; + if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo, + /* compareOnlyBasicChanges */ true)) { + logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED; } + logicalDisplayEventMask + |= updateAndGetMaskForDisplayPropertyChanges(mTempNonOverrideDisplayInfo); } mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask); mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED); diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index c3057ded66eb..7cc178d5ff6c 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -280,6 +280,11 @@ public class DisplayManagerFlags { Flags::committedStateSeparateEvent ); + private final FlagState mDelayImplicitRrRegistrationUntilRrAccessed = new FlagState( + Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED, + Flags::delayImplicitRrRegistrationUntilRrAccessed + ); + /** * @return {@code true} if 'port' is allowed in display layout configuration file. */ @@ -586,7 +591,6 @@ public class DisplayManagerFlags { return mFramerateOverrideTriggersRrCallbacks.isEnabled(); } - /** * @return {@code true} if the flag for sending refresh rate events only for the apps in * foreground is enabled @@ -604,6 +608,13 @@ public class DisplayManagerFlags { } /** + * @return {@code true} if the flag for only explicit subscription for RR changes is enabled + */ + public boolean isDelayImplicitRrRegistrationUntilRrAccessedEnabled() { + return mDelayImplicitRrRegistrationUntilRrAccessed.isEnabled(); + } + + /** * dumps all flagstates * @param pw printWriter */ @@ -660,6 +671,7 @@ public class DisplayManagerFlags { pw.println(" " + mFramerateOverrideTriggersRrCallbacks); pw.println(" " + mRefreshRateEventForForegroundApps); pw.println(" " + mCommittedStateSeparateEvent); + pw.println(" " + mDelayImplicitRrRegistrationUntilRrAccessed); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index f5451307afb7..a0064a9f5d1d 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -474,9 +474,9 @@ flag { description: "Feature flag to trigger the RR callbacks when framerate overridding happens." bug: "390113266" is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -508,3 +508,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "delay_implicit_rr_registration_until_rr_accessed" + namespace: "display_manager" + description: "Feature flag for clients to subscribe to RR changes by either explicitly subscribing for refresh rate changes or request for refresh rate data" + bug: "391828526" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index 1a29150cd40c..940bcb4c6ba1 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -16,6 +16,7 @@ package com.android.server.location.contexthub; +import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.hardware.contexthub.EndpointInfo; @@ -64,7 +65,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub * Internal interface used to invoke client callbacks. */ interface CallbackConsumer { - void accept(IContextHubEndpointCallback callback) throws RemoteException; + void accept(@NonNull IContextHubEndpointCallback callback) throws RemoteException; } /** The context of the service. */ @@ -86,7 +87,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub private final EndpointInfo mHalEndpointInfo; /** The remote callback interface for this endpoint. */ - private final IContextHubEndpointCallback mContextHubEndpointCallback; + @NonNull private final IContextHubEndpointCallback mContextHubEndpointCallback; /** True if this endpoint is registered with the service/HAL. */ @GuardedBy("mRegistrationLock") @@ -158,7 +159,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub IEndpointCommunication hubInterface, ContextHubEndpointManager endpointManager, EndpointInfo halEndpointInfo, - IContextHubEndpointCallback callback, + @NonNull IContextHubEndpointCallback callback, String packageName, String attributionTag, ContextHubTransactionManager transactionManager) { @@ -419,9 +420,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } /* package */ void attachDeathRecipient() throws RemoteException { - if (mContextHubEndpointCallback != null) { - mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */); - } + mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */); } /* package */ void onEndpointSessionOpenRequest( @@ -664,15 +663,13 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub * @return false if the callback threw a RemoteException */ private boolean invokeCallback(CallbackConsumer consumer) { - if (mContextHubEndpointCallback != null) { - acquireWakeLock(); - try { - consumer.accept(mContextHubEndpointCallback); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException while calling endpoint callback", e); - releaseWakeLock(); - return false; - } + acquireWakeLock(); + try { + consumer.accept(mContextHubEndpointCallback); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling endpoint callback", e); + releaseWakeLock(); + return false; } return true; } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java index 30bb8f3fa188..8ab581e1fb7a 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java @@ -17,6 +17,7 @@ package com.android.server.location.contexthub; import android.annotation.IntDef; +import android.annotation.NonNull; import android.content.Context; import android.hardware.contexthub.ContextHubInfo; import android.hardware.contexthub.EndpointInfo; @@ -240,7 +241,7 @@ import java.util.function.Consumer; */ /* package */ IContextHubEndpoint registerEndpoint( HubEndpointInfo pendingEndpointInfo, - IContextHubEndpointCallback callback, + @NonNull IContextHubEndpointCallback callback, String packageName, String attributionTag) throws RemoteException { diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index bf7351cb11db..2c0c55bd8df4 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -792,6 +792,10 @@ public class ContextHubService extends IContextHubService.Stub { Log.e(TAG, "Endpoint manager failed to initialize"); throw new UnsupportedOperationException("Endpoint registration is not supported"); } + if (callback == null) { + Log.e(TAG, "Endpoint callback is invalid"); + throw new IllegalArgumentException("registerEndpoint must have a non-null callback"); + } return mEndpointManager.registerEndpoint( pendingHubEndpointInfo, callback, packageName, attributionTag); } diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java index 177eefb2ef2a..3f75b11befc2 100644 --- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java @@ -29,7 +29,6 @@ import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.flags.Flags; import com.android.server.FgThread; import java.util.Objects; @@ -107,26 +106,19 @@ public class SystemEmergencyHelper extends EmergencyHelper { boolean isInExtensionTime = mEmergencyCallEndRealtimeMs != Long.MIN_VALUE && (SystemClock.elapsedRealtime() - mEmergencyCallEndRealtimeMs) < extensionTimeMs; - if (!Flags.enforceTelephonyFeatureMapping()) { - return mIsInEmergencyCall - || isInExtensionTime - || mTelephonyManager.getEmergencyCallbackMode() - || mTelephonyManager.isInEmergencySmsMode(); - } else { - boolean emergencyCallbackMode = false; - boolean emergencySmsMode = false; - PackageManager pm = mContext.getPackageManager(); - if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) { - emergencyCallbackMode = mTelephonyManager.getEmergencyCallbackMode(); - } - if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) { - emergencySmsMode = mTelephonyManager.isInEmergencySmsMode(); - } - return mIsInEmergencyCall - || isInExtensionTime - || emergencyCallbackMode - || emergencySmsMode; + boolean emergencyCallbackMode = false; + boolean emergencySmsMode = false; + PackageManager pm = mContext.getPackageManager(); + if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) { + emergencyCallbackMode = mTelephonyManager.getEmergencyCallbackMode(); + } + if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) { + emergencySmsMode = mTelephonyManager.isInEmergencySmsMode(); } + return mIsInEmergencyCall + || isInExtensionTime + || emergencyCallbackMode + || emergencySmsMode; } private class EmergencyCallTelephonyCallback extends TelephonyCallback implements diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 0fc182f3f1bb..fff812c038e7 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -42,6 +42,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; +import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE; import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE; import android.annotation.FlaggedApi; @@ -286,7 +287,7 @@ public class PreferencesHelper implements RankingConfig { if (!TAG_RANKING.equals(tag)) return; final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1); - boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE; + boolean upgradeForBubbles = xmlVersion >= XML_VERSION_BUBBLES_UPGRADE; boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION); if (mShowReviewPermissionsNotification && (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) { @@ -337,15 +338,19 @@ public class PreferencesHelper implements RankingConfig { } boolean skipWarningLogged = false; boolean skipGroupWarningLogged = false; - boolean hasSAWPermission = false; - if (upgradeForBubbles && uid != UNKNOWN_UID) { - hasSAWPermission = mAppOps.noteOpNoThrow( - OP_SYSTEM_ALERT_WINDOW, uid, name, null, - "check-notif-bubble") == AppOpsManager.MODE_ALLOWED; - } - int bubblePref = hasSAWPermission - ? BUBBLE_PREFERENCE_ALL - : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE); + int bubblePref = parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, + DEFAULT_BUBBLE_PREFERENCE); + boolean bubbleLocked = (parser.getAttributeInt(null, + ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS) & USER_LOCKED_BUBBLE) + != 0; + if (!bubbleLocked + && upgradeForBubbles + && uid != UNKNOWN_UID + && mAppOps.noteOpNoThrow(OP_SYSTEM_ALERT_WINDOW, uid, name, null, + "check-notif-bubble") == AppOpsManager.MODE_ALLOWED) { + // User hasn't changed bubble pref & the app has SAW, so allow all bubbles. + bubblePref = BUBBLE_PREFERENCE_ALL; + } int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); // when data is loaded from disk it's loaded as USER_ALL, but restored data that diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 29f8243cfe60..ae415196f15e 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -24,6 +24,7 @@ import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.UserIdInt; import android.content.pm.PackageManager; import android.os.CreateAppDataArgs; @@ -548,7 +549,7 @@ public class AppDataHelper { return prepareAppDataFuture; } - void clearAppDataLIF(AndroidPackage pkg, int userId, int flags) { + void clearAppDataLIF(AndroidPackage pkg, @CanBeALL @UserIdInt int userId, int flags) { if (pkg == null) { return; } @@ -559,7 +560,8 @@ public class AppDataHelper { } } - void clearAppDataLeafLIF(String packageName, String volumeUuid, int userId, int flags) { + void clearAppDataLeafLIF(String packageName, String volumeUuid, @CanBeALL @UserIdInt int userId, + int flags) { final Computer snapshot = mPm.snapshotComputer(); final PackageStateInternal packageStateInternal = snapshot.getPackageStateInternal(packageName); diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java index c3af578de369..463989adc98f 100644 --- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java +++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java @@ -332,7 +332,8 @@ public class BackgroundInstallControlService extends SystemService { userId) != PERMISSION_GRANTED) { if(Build.IS_DEBUGGABLE) { - Slog.d(TAG, "handlePackageAdd " + packageName + ": installer doesn't " + Slog.d(TAG, "handlePackageAdd " + packageName + ": installer (" + + installerPackageName + ") doesn't " + "have INSTALL_PACKAGES permission, skipping"); } return; diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 90adb6683496..6bec34ef7063 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -35,6 +35,8 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; +import android.annotation.UserIdInt; import android.app.ApplicationExitInfo; import android.app.ApplicationPackageManager; import android.content.Intent; @@ -562,7 +564,7 @@ final class DeletePackageHelper { } @GuardedBy("mPm.mInstallLock") - private void deleteInstalledPackageLIF(PackageSetting ps, int userId, + private void deleteInstalledPackageLIF(PackageSetting ps, @CanBeALL @UserIdInt int userId, boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles, @NonNull PackageRemovedInfo outInfo, boolean writeSettings) { synchronized (mPm.mLock) { diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java index 11f2059c4267..d66eb814ef66 100644 --- a/services/core/java/com/android/server/pm/PackageFreezer.java +++ b/services/core/java/com/android/server/pm/PackageFreezer.java @@ -18,6 +18,8 @@ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; +import android.annotation.UserIdInt; import android.content.pm.Flags; import android.content.pm.PackageManager; @@ -60,12 +62,12 @@ final class PackageFreezer implements AutoCloseable { } } - PackageFreezer(String packageName, int userId, String killReason, + PackageFreezer(String packageName, @CanBeALL @UserIdInt int userId, String killReason, PackageManagerService pm, int exitInfoReason, @Nullable InstallRequest request) { this(packageName, userId, killReason, pm, exitInfoReason, request, false); } - PackageFreezer(String packageName, int userId, String killReason, + PackageFreezer(String packageName, @CanBeALL @UserIdInt int userId, String killReason, PackageManagerService pm, int exitInfoReason, @Nullable InstallRequest request, boolean waitAppKilled) { mPm = pm; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2464a291b4dd..91a1c9c12cb8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -50,6 +50,7 @@ import android.annotation.AppIdInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.StringRes; import android.annotation.UserIdInt; import android.annotation.WorkerThread; @@ -1590,7 +1591,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService scheduleWritePackageRestrictions(userId); } - void scheduleWritePackageRestrictions(int userId) { + void scheduleWritePackageRestrictions(@CanBeALL @UserIdInt int userId) { invalidatePackageInfoCache(); if (userId == UserHandle.USER_ALL) { synchronized (mDirtyUsers) { @@ -3074,7 +3075,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @NonNull - int[] resolveUserIds(int userId) { + int[] resolveUserIds(@CanBeALL @UserIdInt int userId) { return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId }; } @@ -3112,7 +3113,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } void killApplication(String pkgName, @AppIdInt int appId, - @UserIdInt int userId, String reason, int exitInfoReason) { + @CanBeALL @UserIdInt int userId, String reason, int exitInfoReason) { // Request the ActivityManager to kill the process(only for existing packages) // so that we do not end up in a confused state while the user is still using the older // version of the application while the new one gets installed. @@ -3131,7 +3132,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } void killApplicationSync(String pkgName, @AppIdInt int appId, - @UserIdInt int userId, String reason, int exitInfoReason) { + @CanBeALL @UserIdInt int userId, String reason, int exitInfoReason) { ActivityManagerInternal mAmi = LocalServices.getService(ActivityManagerInternal.class); if (Thread.holdsLock(mLock) || mAmi == null) { // holds PM's lock, go back killApplication to avoid it run into watchdog reset. @@ -3385,7 +3386,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } // TODO(b/261957226): centralise this logic in DPM - boolean isPackageDeviceAdmin(String packageName, int userId) { + boolean isPackageDeviceAdmin(String packageName, @CanBeALL @UserIdInt int userId) { final IDevicePolicyManager dpm = getDevicePolicyManager(); final DevicePolicyManagerInternal dpmi = mInjector.getLocalService(DevicePolicyManagerInternal.class); @@ -3555,7 +3556,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService /** This method takes a specific user id as well as UserHandle.USER_ALL. */ @GuardedBy("mLock") void clearPackagePreferredActivitiesLPw(String packageName, - @NonNull SparseBooleanArray outUserChanged, int userId) { + @NonNull SparseBooleanArray outUserChanged, @CanBeALL @UserIdInt int userId) { mSettings.clearPackagePreferredActivities(packageName, outUserChanged, userId); } @@ -4388,14 +4389,14 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } - public PackageFreezer freezePackage(String packageName, int userId, String killReason, - int exitInfoReason, InstallRequest request) { + public PackageFreezer freezePackage(String packageName, @CanBeALL @UserIdInt int userId, + String killReason, int exitInfoReason, InstallRequest request) { return freezePackage(packageName, userId, killReason, exitInfoReason, request, /* waitAppKilled= */ false); } - private PackageFreezer freezePackage(String packageName, int userId, String killReason, - int exitInfoReason, InstallRequest request, boolean waitAppKilled) { + private PackageFreezer freezePackage(String packageName, @CanBeALL @UserIdInt int userId, + String killReason, int exitInfoReason, InstallRequest request, boolean waitAppKilled) { return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason, request, waitAppKilled); } diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java index 41d2aeb9b168..fa56596bf62d 100644 --- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java +++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java @@ -25,6 +25,7 @@ import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED; import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Intent; @@ -115,7 +116,8 @@ final class PreferredActivityHelper { } /** This method takes a specific user id as well as UserHandle.USER_ALL. */ - public void clearPackagePreferredActivities(String packageName, int userId) { + public void clearPackagePreferredActivities(String packageName, + @CanBeALL @UserIdInt int userId) { final SparseBooleanArray changedUsers = new SparseBooleanArray(); synchronized (mPm.mLock) { mPm.clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId); diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index f01a74e8d60d..22b4ec7b51b6 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -30,6 +30,8 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; +import android.annotation.UserIdInt; import android.content.pm.PackageManager; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; @@ -256,7 +258,8 @@ final class RemovePackageHelper { * Make sure this flag is set for partially installed apps. If not it's meaningless to * delete a partially installed application. */ - public void clearPackageStateForUserLIF(PackageSetting ps, int userId, int flags) { + public void clearPackageStateForUserLIF(PackageSetting ps, @CanBeALL @UserIdInt int userId, + int flags) { final String packageName = ps.getPackageName(); // Step 1: always destroy app profiles except when explicitly preserved if ((flags & Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES) == 0) { @@ -370,13 +373,13 @@ final class RemovePackageHelper { * This method deletes the package from internal data structures such as mPackages / mSettings. * * @param targetUserId indicates the target user of the deletion. It equals to - * {@link UserHandle.USER_ALL} if the deletion was initiated for all users, + * {@link UserHandle#USER_ALL} if the deletion was initiated for all users, * otherwise it equals to the specific user id that the deletion was meant * for. */ @GuardedBy("mPm.mInstallLock") - public void removePackageDataLIF(final PackageSetting deletedPs, int targetUserId, - @NonNull int[] allUserHandles, + public void removePackageDataLIF(final PackageSetting deletedPs, + @CanBeALL @UserIdInt int targetUserId, @NonNull int[] allUserHandles, @NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) { String packageName = deletedPs.getPackageName(); if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs); @@ -482,7 +485,8 @@ final class RemovePackageHelper { } } - private static boolean shouldDeletePackageSetting(PackageSetting deletedPs, int userId, + private static boolean shouldDeletePackageSetting(PackageSetting deletedPs, + @CanBeALL @UserIdInt int userId, int[] allUserHandles, int flags) { if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) { return false; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 485a28070bc5..92257f1ee2dd 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -35,6 +35,7 @@ import static com.android.server.pm.SharedUidMigration.BEST_EFFORT; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.UserIdInt; import android.app.compat.ChangeIdStateCache; import android.content.ComponentName; @@ -6639,7 +6640,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile /** This method takes a specific user id as well as UserHandle.USER_ALL. */ void clearPackagePreferredActivities(String packageName, - @NonNull SparseBooleanArray outUserChanged, int userId) { + @NonNull SparseBooleanArray outUserChanged, @CanBeALL @UserIdInt int userId) { boolean changed = false; ArrayList<PreferredActivity> removed = null; for (int i = 0; i < mPreferredActivities.size(); i++) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 5c5a9c1b6c05..ac19ea12c6a4 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -37,6 +37,7 @@ import android.Manifest; import android.annotation.AppIdInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppOpsManager; @@ -765,7 +766,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void onPackageUninstalled(@NonNull String packageName, int appId, @NonNull PackageState packageState, @Nullable AndroidPackage pkg, - @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId) { + @NonNull List<AndroidPackage> sharedUserPkgs, @CanBeALL @UserIdInt int userId) { if (userId != UserHandle.USER_ALL) { final int[] userIds = getAllUserIds(); if (!ArrayUtils.contains(userIds, userId)) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index e51ec04e60fe..33d57d5cc2b9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -65,6 +65,7 @@ import android.annotation.AppIdInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppOpsManager; @@ -5284,7 +5285,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @Override public void onPackageUninstalled(@NonNull String packageName, int appId, @NonNull PackageState packageState, @Nullable AndroidPackage pkg, - @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId) { + @NonNull List<AndroidPackage> sharedUserPkgs, @CanBeALL @UserIdInt int userId) { Objects.requireNonNull(packageState, "packageState"); Objects.requireNonNull(packageName, "packageName"); Objects.requireNonNull(sharedUserPkgs, "sharedUserPkgs"); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index 3d295f773805..f2491d949e6b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -19,6 +19,7 @@ package com.android.server.pm.permission; import android.annotation.AppIdInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.PackageManager; @@ -662,5 +663,5 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte */ void onPackageUninstalled(@NonNull String packageName, int appId, @NonNull PackageState packageState, @Nullable AndroidPackage pkg, - @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId); + @NonNull List<AndroidPackage> sharedUserPkgs, @CanBeALL @UserIdInt int userId); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index a5c12840a645..ad765c8a0d54 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -18,6 +18,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SpecialUsers.CanBeALL; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.content.pm.PackageInstaller.SessionParams; @@ -325,7 +326,7 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) void onPackageUninstalled(@NonNull String packageName, int appId, @Nullable PackageState packageState, @Nullable AndroidPackage pkg, - @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId); + @NonNull List<AndroidPackage> sharedUserPkgs, @CanBeALL @UserIdInt int userId); /** * The permission-related parameters passed in for package installation. diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 1d62087428d8..2cf6b7efcb48 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -2911,19 +2911,7 @@ public class BatteryStatsImpl extends BatteryStats { return false; } - mCounter.getCounts(counts, procState); - - // Return counts only if at least one of the elements is non-zero. - for (int i = counts.length - 1; i >= 0; --i) { - if (counts[i] != 0) { - return true; - } - } - return false; - } - - public void logState(Printer pw, String prefix) { - pw.println(prefix + "mCounter=" + mCounter); + return mCounter.getCounts(counts, procState); } /** diff --git a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java index 5f93bdf07f47..a550f2d820a9 100644 --- a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java @@ -68,11 +68,15 @@ class AmbientDisplayPowerStatsProcessor extends PowerStatsProcessor { // processor. All that remains to be done is copy the estimates over. MultiStateStats.States.forEachTrackedStateCombination(deviceStateConfig, states -> { - screenStats.getDeviceStats(mTmpScreenStats, states); + if (!screenStats.getDeviceStats(mTmpScreenStats, states)) { + return; + } double power = mScreenPowerStatsLayout.getScreenDozePowerEstimate(mTmpScreenStats); - mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, power); - stats.setDeviceStats(states, mTmpDeviceStats); + if (power != 0) { + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, power); + stats.setDeviceStats(states, mTmpDeviceStats); + } }); } } diff --git a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java index 6ce2cf738192..98280beced9f 100644 --- a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java @@ -119,6 +119,8 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength]; mWakelockDescriptor = null; + + initEnergyConsumerToPowerBracketMaps(); } /** @@ -157,9 +159,6 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { if (mPlan == null) { mPlan = new PowerEstimationPlan(stats.getConfig()); - if (mStatsLayout.getEnergyConsumerCount() != 0) { - initEnergyConsumerToPowerBracketMaps(); - } } Intermediates intermediates = new Intermediates(); @@ -255,6 +254,10 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { */ private void initEnergyConsumerToPowerBracketMaps() { int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); + if (energyConsumerCount == 0) { + return; + } + int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount]; @@ -404,7 +407,10 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { deviceStatsIntermediates.timeByBracket = new long[powerBracketCount]; deviceStatsIntermediates.powerByBracket = new double[powerBracketCount]; - stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues); + if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues)) { + continue; + } + for (int step = 0; step < cpuScalingStepCount; step++) { if (intermediates.timeByScalingStep[step] == 0) { continue; @@ -429,16 +435,19 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { } if (wakelockStats != null) { - wakelockStats.getDeviceStats(mTmpWakelockDeviceStats, - deviceStateEstimation.stateValues); - double wakelockPowerEstimate = mWakelockPowerStatsLayout.getDevicePowerEstimate( - mTmpWakelockDeviceStats); - power = Math.max(0, power - wakelockPowerEstimate); + if (wakelockStats.getDeviceStats(mTmpWakelockDeviceStats, + deviceStateEstimation.stateValues)) { + double wakelockPowerEstimate = mWakelockPowerStatsLayout.getDevicePowerEstimate( + mTmpWakelockDeviceStats); + power = Math.max(0, power - wakelockPowerEstimate); + } } - deviceStatsIntermediates.power = power; - mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power); - stats.setDeviceStats(deviceStateEstimation.stateValues, mTmpDeviceStatsArray); + if (power != 0) { + deviceStatsIntermediates.power = power; + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power); + stats.setDeviceStats(deviceStateEstimation.stateValues, mTmpDeviceStatsArray); + } } } @@ -538,11 +547,12 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { } if (wakelockStats != null) { - wakelockStats.getUidStats(mTmpWakelockUidStats, uid, - proportionalEstimate.stateValues); - double wakelockPowerEstimate = mWakelockPowerStatsLayout.getUidPowerEstimate( - mTmpWakelockUidStats); - power = Math.max(0, power - wakelockPowerEstimate); + if (wakelockStats.getUidStats(mTmpWakelockUidStats, uid, + proportionalEstimate.stateValues)) { + double wakelockPowerEstimate = mWakelockPowerStatsLayout.getUidPowerEstimate( + mTmpWakelockUidStats); + power = Math.max(0, power - wakelockPowerEstimate); + } } if (power != 0) { diff --git a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java index a544daad82f1..5883505326ad 100644 --- a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java @@ -259,7 +259,9 @@ class MobileRadioPowerStatsProcessor extends PowerStatsProcessor { stats.forEachStateStatsKey(key -> { RxTxPowerEstimators estimators = mRxTxPowerEstimators.get(key); - stats.getStateStats(mTmpStateStatsArray, key, deviceStates); + if (!stats.getStateStats(mTmpStateStatsArray, key, deviceStates)) { + return; + } long rxTime = mStatsLayout.getStateRxTime(mTmpStateStatsArray); intermediates.rxPower += estimators.mRxPowerEstimator.calculatePower(rxTime); for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) { diff --git a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java index 69325757c79d..0038943d7cf8 100644 --- a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java +++ b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java @@ -16,6 +16,7 @@ package com.android.server.power.stats.processor; +import android.annotation.CheckResult; import android.annotation.Nullable; import android.util.Slog; @@ -342,10 +343,12 @@ class MultiStateStats { } /** - * Returns accumulated stats for the specified composite state. + * Returns accumulated stats for the specified composite state or false if the results are + * all zeros. */ - void getStats(long[] outValues, int[] states) { - mCounter.getCounts(outValues, mFactory.getSerialState(states)); + @CheckResult + boolean getStats(long[] outValues, int[] states) { + return mCounter.getCounts(outValues, mFactory.getSerialState(states)); } /** @@ -389,15 +392,7 @@ class MultiStateStats { private void writeXmlForStates(TypedXmlSerializer serializer, int[] states, long[] values) throws IOException { - mCounter.getCounts(values, mFactory.getSerialState(states)); - boolean nonZero = false; - for (long value : values) { - if (value != 0) { - nonZero = true; - break; - } - } - if (!nonZero) { + if (!mCounter.getCounts(values, mFactory.getSerialState(states))) { return; } @@ -470,15 +465,7 @@ class MultiStateStats { StringBuilder sb = new StringBuilder(); long[] values = new long[mCounter.getArrayLength()]; States.forEachTrackedStateCombination(mFactory.mStates, states -> { - mCounter.getCounts(values, mFactory.getSerialState(states)); - boolean nonZero = false; - for (long value : values) { - if (value != 0) { - nonZero = true; - break; - } - } - if (!nonZero) { + if (!mCounter.getCounts(values, mFactory.getSerialState(states))) { return; } diff --git a/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java index 3957ae0862dc..ad628e4632e1 100644 --- a/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java @@ -69,12 +69,15 @@ class PhoneCallPowerStatsProcessor extends PowerStatsProcessor { // processor. All that remains to be done is copy the estimates over. MultiStateStats.States.forEachTrackedStateCombination(deviceStateConfig, states -> { - mobileRadioStats.getDeviceStats(mTmpMobileRadioDeviceStats, states); - double callPowerEstimate = - mMobileRadioStatsLayout.getDeviceCallPowerEstimate( - mTmpMobileRadioDeviceStats); - mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, callPowerEstimate); - stats.setDeviceStats(states, mTmpDeviceStats); + if (!mobileRadioStats.getDeviceStats(mTmpMobileRadioDeviceStats, states)) { + return; + } + double callPowerEstimate = mMobileRadioStatsLayout.getDeviceCallPowerEstimate( + mTmpMobileRadioDeviceStats); + if (callPowerEstimate != 0) { + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, callPowerEstimate); + stats.setDeviceStats(states, mTmpDeviceStats); + } }); } } diff --git a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java index f9b9da9171cb..d285a5987710 100644 --- a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java @@ -16,6 +16,7 @@ package com.android.server.power.stats.processor; +import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.BatteryStats; @@ -312,6 +313,11 @@ class PowerComponentAggregatedPowerStats { return uids; } + /** + * Populates outValues with the stats for the specified states. If the stats are all 0, + * returns false, leaving outValues unchanged. + */ + @CheckResult boolean getDeviceStats(long[] outValues, int[] deviceStates) { if (deviceStates.length != mDeviceStateConfig.length) { throw new IllegalArgumentException( @@ -319,12 +325,16 @@ class PowerComponentAggregatedPowerStats { + " expected: " + mDeviceStateConfig.length); } if (mDeviceStats != null) { - mDeviceStats.getStats(outValues, deviceStates); - return true; + return mDeviceStats.getStats(outValues, deviceStates); } return false; } + /** + * Populates outValues with the stats for the specified key and device states. If the stats + * are all 0, returns false, leaving outValues unchanged. + */ + @CheckResult boolean getStateStats(long[] outValues, int key, int[] deviceStates) { if (deviceStates.length != mDeviceStateConfig.length) { throw new IllegalArgumentException( @@ -333,8 +343,7 @@ class PowerComponentAggregatedPowerStats { } MultiStateStats stateStats = mStateStats.get(key); if (stateStats != null) { - stateStats.getStats(outValues, deviceStates); - return true; + return stateStats.getStats(outValues, deviceStates); } return false; } @@ -345,6 +354,11 @@ class PowerComponentAggregatedPowerStats { } } + /** + * Populates outValues with the stats for the specified UID and UID states. If the stats are + * all 0, returns false, leaving outValues unchanged. + */ + @CheckResult boolean getUidStats(long[] outValues, int uid, int[] uidStates) { if (uidStates.length != mUidStateConfig.length) { throw new IllegalArgumentException( @@ -353,8 +367,7 @@ class PowerComponentAggregatedPowerStats { } UidStats uidStats = mUidStats.get(uid); if (uidStats != null && uidStats.stats != null) { - uidStats.stats.getStats(outValues, uidStates); - return true; + return uidStats.stats.getStats(outValues, uidStates); } return false; } @@ -578,15 +591,7 @@ class PowerComponentAggregatedPowerStats { long[] values = new long[stats.getDimensionCount()]; MultiStateStats.States[] stateInfo = stats.getStates(); MultiStateStats.States.forEachTrackedStateCombination(stateInfo, states -> { - stats.getStats(values, states); - boolean nonZero = false; - for (long value : values) { - if (value != 0) { - nonZero = true; - break; - } - } - if (!nonZero) { + if (!stats.getStats(values, states)) { return; } diff --git a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java index ba728d36d561..284e6a9fce89 100644 --- a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java @@ -298,12 +298,16 @@ class SensorPowerStatsProcessor extends PowerStatsProcessor { continue; } - if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) { + double power = ((Intermediates) estimation.intermediates).power; + if (power == 0) { continue; } - mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, - ((Intermediates) estimation.intermediates).power); + if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) { + Arrays.fill(mTmpDeviceStatsArray, 0); + } + + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power); stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray); } } diff --git a/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java b/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java index 9d60a576d9bc..0f8212e23c20 100644 --- a/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java +++ b/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java @@ -136,4 +136,9 @@ public final class ImmutableVolumeInfo { public boolean isVisibleForWrite(int userId) { return mVolumeInfo.isVisibleForWrite(userId); } + + @Override + public String toString() { + return mVolumeInfo.toString(); + } } diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java index 342b864c6473..281aeb68f224 100644 --- a/services/core/java/com/android/server/storage/StorageSessionController.java +++ b/services/core/java/com/android/server/storage/StorageSessionController.java @@ -156,14 +156,15 @@ public final class StorageSessionController { StorageUserConnection connection = null; synchronized (mLock) { connection = mConnections.get(connectionUserId); - if (connection != null) { - Slog.i(TAG, "Notifying volume state changed for session with id: " + sessionId); - connection.notifyVolumeStateChanged(sessionId, - vol.buildStorageVolume(mContext, vol.getMountUserId(), false)); - } else { - Slog.w(TAG, "No available storage user connection for userId : " - + connectionUserId); - } + } + + if (connection != null) { + Slog.i(TAG, "Notifying volume state changed for session with id: " + sessionId); + connection.notifyVolumeStateChanged(sessionId, + vol.buildStorageVolume(mContext, vol.getMountUserId(), false)); + } else { + Slog.w(TAG, "No available storage user connection for userId : " + + connectionUserId); } } @@ -225,16 +226,18 @@ public final class StorageSessionController { String sessionId = vol.getId(); int userId = getConnectionUserIdForVolume(vol); + StorageUserConnection connection = null; synchronized (mLock) { - StorageUserConnection connection = mConnections.get(userId); - if (connection != null) { - Slog.i(TAG, "Removed session for vol with id: " + sessionId); - connection.removeSession(sessionId); - return connection; - } else { - Slog.w(TAG, "Session already removed for vol with id: " + sessionId); - return null; - } + connection = mConnections.get(userId); + } + + if (connection != null) { + Slog.i(TAG, "Removed session for vol with id: " + sessionId); + connection.removeSession(sessionId); + return connection; + } else { + Slog.w(TAG, "Session already removed for vol with id: " + sessionId); + return null; } } diff --git a/services/core/java/com/android/server/storage/WatchedVolumeInfo.java b/services/core/java/com/android/server/storage/WatchedVolumeInfo.java index 4124cfb4f092..94e52cd1033a 100644 --- a/services/core/java/com/android/server/storage/WatchedVolumeInfo.java +++ b/services/core/java/com/android/server/storage/WatchedVolumeInfo.java @@ -203,4 +203,9 @@ public class WatchedVolumeInfo extends WatchableImpl { public boolean isVisibleForWrite(int userId) { return mVolumeInfo.isVisibleForWrite(userId); } + + @Override + public String toString() { + return mVolumeInfo.toString(); + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java index 07d9ad16aca5..da6478bd3395 100644 --- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java @@ -124,6 +124,8 @@ abstract class AbstractVibratorStep extends Step { Slog.d(VibrationThread.TAG, "Turning off vibrator " + getVibratorId()); } + // Make sure we ignore any pending callback from old vibration commands. + conductor.nextVibratorCallbackStepId(getVibratorId()); controller.off(); getVibration().stats.reportVibratorOff(); mPendingVibratorOffDeadline = 0; diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java index e495af59a2f9..b3eead109999 100644 --- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java @@ -73,7 +73,8 @@ final class ComposePrimitivesVibratorStep extends AbstractComposedVibratorStep { PrimitiveSegment[] primitivesArray = primitives.toArray(new PrimitiveSegment[primitives.size()]); - long vibratorOnResult = controller.on(primitivesArray, getVibration().id); + int stepId = conductor.nextVibratorCallbackStepId(getVibratorId()); + long vibratorOnResult = controller.on(primitivesArray, getVibration().id, stepId); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportComposePrimitives(vibratorOnResult, primitivesArray); diff --git a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java index bb8e6eed5707..7b41457b5016 100644 --- a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java +++ b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java @@ -72,7 +72,8 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep { + controller.getVibratorInfo().getId()); } PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]); - long vibratorOnResult = controller.on(pwlesArray, getVibration().id); + int stepId = conductor.nextVibratorCallbackStepId(getVibratorId()); + long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray); diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java index e8952fafaf77..d003251ef307 100644 --- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java @@ -71,7 +71,8 @@ final class ComposePwleVibratorStep extends AbstractComposedVibratorStep { + controller.getVibratorInfo().getId()); } RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]); - long vibratorOnResult = controller.on(pwlesArray, getVibration().id); + int stepId = conductor.nextVibratorCallbackStepId(getVibratorId()); + long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray); diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java index a92ac679b0f4..b2cc1f60ca1d 100644 --- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java @@ -148,7 +148,7 @@ final class ExternalVibrationSession extends Vibration } @Override - public void notifyVibratorCallback(int vibratorId, long vibrationId) { + public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) { // ignored, external control does not expect callbacks from the vibrator } diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java index 4b23216258af..88bb181781bf 100644 --- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java @@ -64,7 +64,8 @@ final class PerformPrebakedVibratorStep extends AbstractComposedVibratorStep { } VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId()); - long vibratorOnResult = controller.on(prebaked, getVibration().id); + int stepId = conductor.nextVibratorCallbackStepId(getVibratorId()); + long vibratorOnResult = controller.on(prebaked, getVibration().id, stepId); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportPerformEffect(vibratorOnResult, prebaked); diff --git a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java index 407f3d996798..d4bcc36aee18 100644 --- a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java @@ -49,7 +49,8 @@ final class PerformVendorEffectVibratorStep extends AbstractVibratorStep { public List<Step> play() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformVendorEffectVibratorStep"); try { - long vibratorOnResult = controller.on(effect, getVibration().id); + int stepId = conductor.nextVibratorCallbackStepId(getVibratorId()); + long vibratorOnResult = controller.on(effect, getVibration().id, stepId); vibratorOnResult = Math.min(vibratorOnResult, VENDOR_EFFECT_MAX_DURATION_MS); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportPerformVendorEffect(vibratorOnResult); diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java index 8478e7743183..26b9595e60cd 100644 --- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.os.SystemClock; import android.os.Trace; import android.os.VibrationEffect; +import android.os.vibrator.Flags; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; @@ -49,6 +50,10 @@ final class SetAmplitudeVibratorStep extends AbstractComposedVibratorStep { @Override public boolean acceptVibratorCompleteCallback(int vibratorId) { + if (Flags.fixVibrationThreadCallbackHandling()) { + // TODO: remove this method once flag removed. + return super.acceptVibratorCompleteCallback(vibratorId); + } // Ensure the super method is called and will reset the off timeout and boolean flag. // This is true if the vibrator was ON and this callback has the same vibratorId. if (!super.acceptVibratorCompleteCallback(vibratorId)) { @@ -161,7 +166,8 @@ final class SetAmplitudeVibratorStep extends AbstractComposedVibratorStep { "Turning on vibrator " + controller.getVibratorInfo().getId() + " for " + duration + "ms"); } - long vibratorOnResult = controller.on(duration, getVibration().id); + int stepId = conductor.nextVibratorCallbackStepId(getVibratorId()); + long vibratorOnResult = controller.on(duration, getVibration().id, stepId); handleVibratorOnResult(vibratorOnResult); getVibration().stats.reportVibratorOn(vibratorOnResult); return vibratorOnResult; diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java index 628221b09d77..309eb8c3b099 100644 --- a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java @@ -137,13 +137,13 @@ final class SingleVibrationSession implements VibrationSession, IBinder.DeathRec } @Override - public void notifyVibratorCallback(int vibratorId, long vibrationId) { + public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) { if (vibrationId != mVibration.id) { return; } synchronized (mLock) { if (mConductor != null) { - mConductor.notifyVibratorComplete(vibratorId); + mConductor.notifyVibratorComplete(vibratorId, stepId); } } } diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java index 64b52b175252..bda3d442956b 100644 --- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java @@ -218,8 +218,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub } @Override - public void notifyVibratorCallback(int vibratorId, long vibrationId) { - Slog.d(TAG, "Vibration callback received for vibration " + vibrationId + public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) { + Slog.d(TAG, "Vibration callback received for vibration " + vibrationId + " step " + stepId + " on vibrator " + vibratorId + ", ignoring..."); } diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java index ae95a70e2a4f..23715e392580 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSession.java +++ b/services/core/java/com/android/server/vibrator/VibrationSession.java @@ -106,7 +106,7 @@ interface VibrationSession { * complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete, * since its playback might have one or more interactions with the vibrator hardware. */ - void notifyVibratorCallback(int vibratorId, long vibrationId); + void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId); /** * Notify all synced vibrators have completed the last synchronized command during the playback diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java index 1e20debe156d..36e13224a476 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java +++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java @@ -30,6 +30,7 @@ import android.os.vibrator.VibrationEffectSegment; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.server.vibrator.VibrationSession.Status; @@ -93,6 +94,8 @@ final class VibrationStepConductor { private final Object mLock = new Object(); @GuardedBy("mLock") private final IntArray mSignalVibratorsComplete; + @GuardedBy("mLock") + private final SparseIntArray mSignalVibratorStepIds; @Nullable @GuardedBy("mLock") private Vibration.EndInfo mSignalCancel = null; @@ -121,6 +124,8 @@ final class VibrationStepConductor { this.vibratorManagerHooks = vibratorManagerHooks; this.mSignalVibratorsComplete = new IntArray(mDeviceAdapter.getAvailableVibratorIds().length); + this.mSignalVibratorStepIds = + new SparseIntArray(mDeviceAdapter.getAvailableVibratorIds().length); } @Nullable @@ -418,7 +423,7 @@ final class VibrationStepConductor { * <p>This is a lightweight method intended to be called directly via native callbacks. * The state update is recorded for processing on the main execution thread (VibrationThread). */ - public void notifyVibratorComplete(int vibratorId) { + public void notifyVibratorComplete(int vibratorId, long stepId) { // HAL callbacks may be triggered directly within HAL calls, so these notifications // could be on the VibrationThread as it calls the HAL, or some other executor later. // Therefore no thread assertion is made here. @@ -428,6 +433,14 @@ final class VibrationStepConductor { } synchronized (mLock) { + if (Flags.fixVibrationThreadCallbackHandling() + && mSignalVibratorStepIds.get(vibratorId) != stepId) { + if (DEBUG) { + Slog.d(TAG, "Vibrator " + vibratorId + " callback for step=" + stepId + + " ignored, current step=" + mSignalVibratorStepIds.get(vibratorId)); + } + return; + } mSignalVibratorsComplete.add(vibratorId); mLock.notify(); } @@ -645,6 +658,26 @@ final class VibrationStepConductor { } } + /** + * Updates and returns the next step id value to be used in vibrator commands. + * + * <p>This new step id will be kept by this conductor to filter out old callbacks that might be + * triggered too late by the HAL, preventing them from affecting the ongoing vibration playback. + */ + public int nextVibratorCallbackStepId(int vibratorId) { + if (!Flags.fixVibrationThreadCallbackHandling()) { + return 0; + } + if (Build.IS_DEBUGGABLE) { + expectIsVibrationThread(true); + } + synchronized (mLock) { + int stepId = mSignalVibratorStepIds.get(vibratorId) + 1; + mSignalVibratorStepIds.put(vibratorId, stepId); + return stepId; + } + } + private static CombinedVibration.Sequential toSequential(CombinedVibration effect) { if (effect instanceof CombinedVibration.Sequential) { return (CombinedVibration.Sequential) effect; diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index acb31ceb4027..ab13b0e88d04 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -72,7 +72,7 @@ final class VibratorController { public interface OnVibrationCompleteListener { /** Callback triggered when an active vibration command is complete. */ - void onComplete(int vibratorId, long vibrationId); + void onComplete(int vibratorId, long vibrationId, long stepId); } /** Representation of the vibrator state based on the interactions through this controller. */ @@ -285,11 +285,11 @@ final class VibratorController { * @return The positive duration of the vibration started, if successful, zero if the vibrator * do not support the input or a negative number if the operation failed. */ - public long on(long milliseconds, long vibrationId) { + public long on(long milliseconds, long vibrationId, long stepId) { Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on"); try { synchronized (mLock) { - long duration = mNativeWrapper.on(milliseconds, vibrationId); + long duration = mNativeWrapper.on(milliseconds, vibrationId, stepId); if (duration > 0) { mCurrentAmplitude = -1; updateStateAndNotifyListenersLocked(VibratorState.VIBRATING); @@ -310,7 +310,7 @@ final class VibratorController { * @return The positive duration of the vibration started, if successful, zero if the vibrator * do not support the input or a negative number if the operation failed. */ - public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId) { + public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId, long stepId) { Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (vendor)"); synchronized (mLock) { Parcel vendorData = Parcel.obtain(); @@ -319,7 +319,7 @@ final class VibratorController { vendorData.setDataPosition(0); long duration = mNativeWrapper.performVendorEffect(vendorData, vendorEffect.getEffectStrength(), vendorEffect.getScale(), - vendorEffect.getAdaptiveScale(), vibrationId); + vendorEffect.getAdaptiveScale(), vibrationId, stepId); if (duration > 0) { mCurrentAmplitude = -1; updateStateAndNotifyListenersLocked(VibratorState.VIBRATING); @@ -341,12 +341,12 @@ final class VibratorController { * @return The positive duration of the vibration started, if successful, zero if the vibrator * do not support the input or a negative number if the operation failed. */ - public long on(PrebakedSegment prebaked, long vibrationId) { + public long on(PrebakedSegment prebaked, long vibrationId, long stepId) { Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Prebaked)"); try { synchronized (mLock) { long duration = mNativeWrapper.perform(prebaked.getEffectId(), - prebaked.getEffectStrength(), vibrationId); + prebaked.getEffectStrength(), vibrationId, stepId); if (duration > 0) { mCurrentAmplitude = -1; updateStateAndNotifyListenersLocked(VibratorState.VIBRATING); @@ -367,14 +367,14 @@ final class VibratorController { * @return The positive duration of the vibration started, if successful, zero if the vibrator * do not support the input or a negative number if the operation failed. */ - public long on(PrimitiveSegment[] primitives, long vibrationId) { + public long on(PrimitiveSegment[] primitives, long vibrationId, long stepId) { Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Primitive)"); try { if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { return 0; } synchronized (mLock) { - long duration = mNativeWrapper.compose(primitives, vibrationId); + long duration = mNativeWrapper.compose(primitives, vibrationId, stepId); if (duration > 0) { mCurrentAmplitude = -1; updateStateAndNotifyListenersLocked(VibratorState.VIBRATING); @@ -394,7 +394,7 @@ final class VibratorController { * * @return The duration of the effect playing, or 0 if unsupported. */ - public long on(RampSegment[] primitives, long vibrationId) { + public long on(RampSegment[] primitives, long vibrationId, long stepId) { Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE)"); try { if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) { @@ -402,7 +402,8 @@ final class VibratorController { } synchronized (mLock) { int braking = mVibratorInfo.getDefaultBraking(); - long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId); + long duration = mNativeWrapper.composePwle( + primitives, braking, vibrationId, stepId); if (duration > 0) { mCurrentAmplitude = -1; updateStateAndNotifyListenersLocked(VibratorState.VIBRATING); @@ -422,14 +423,14 @@ final class VibratorController { * * @return The duration of the effect playing, or 0 if unsupported. */ - public long on(PwlePoint[] pwlePoints, long vibrationId) { + public long on(PwlePoint[] pwlePoints, long vibrationId, long stepId) { Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE v2)"); try { if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) { return 0; } synchronized (mLock) { - long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId); + long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId, stepId); if (duration > 0) { mCurrentAmplitude = -1; updateStateAndNotifyListenersLocked(VibratorState.VIBRATING); @@ -544,26 +545,27 @@ final class VibratorController { private static native boolean isAvailable(long nativePtr); - private static native long on(long nativePtr, long milliseconds, long vibrationId); + private static native long on(long nativePtr, long milliseconds, long vibrationId, + long stepId); private static native void off(long nativePtr); private static native void setAmplitude(long nativePtr, float amplitude); private static native long performEffect(long nativePtr, long effect, long strength, - long vibrationId); + long vibrationId, long stepId); private static native long performVendorEffect(long nativePtr, Parcel vendorData, - long strength, float scale, float adaptiveScale, long vibrationId); + long strength, float scale, float adaptiveScale, long vibrationId, long stepId); private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect, - long vibrationId); + long vibrationId, long stepId); private static native long performPwleEffect(long nativePtr, RampSegment[] effect, - int braking, long vibrationId); + int braking, long vibrationId, long stepId); private static native long performPwleV2Effect(long nativePtr, PwlePoint[] effect, - long vibrationId); + long vibrationId, long stepId); private static native void setExternalControl(long nativePtr, boolean enabled); @@ -595,8 +597,8 @@ final class VibratorController { } /** Turns vibrator on for given time. */ - public long on(long milliseconds, long vibrationId) { - return on(mNativePtr, milliseconds, vibrationId); + public long on(long milliseconds, long vibrationId, long stepId) { + return on(mNativePtr, milliseconds, vibrationId, stepId); } /** Turns vibrator off. */ @@ -610,30 +612,31 @@ final class VibratorController { } /** Turns vibrator on to perform one of the supported effects. */ - public long perform(long effect, long strength, long vibrationId) { - return performEffect(mNativePtr, effect, strength, vibrationId); + public long perform(long effect, long strength, long vibrationId, long stepId) { + return performEffect(mNativePtr, effect, strength, vibrationId, stepId); } /** Turns vibrator on to perform a vendor-specific effect. */ public long performVendorEffect(Parcel vendorData, long strength, float scale, - float adaptiveScale, long vibrationId) { + float adaptiveScale, long vibrationId, long stepId) { return performVendorEffect(mNativePtr, vendorData, strength, scale, adaptiveScale, - vibrationId); + vibrationId, stepId); } /** Turns vibrator on to perform effect composed of give primitives effect. */ - public long compose(PrimitiveSegment[] primitives, long vibrationId) { - return performComposedEffect(mNativePtr, primitives, vibrationId); + public long compose(PrimitiveSegment[] primitives, long vibrationId, long stepId) { + return performComposedEffect(mNativePtr, primitives, vibrationId, stepId); } /** Turns vibrator on to perform PWLE effect composed of given primitives. */ - public long composePwle(RampSegment[] primitives, int braking, long vibrationId) { - return performPwleEffect(mNativePtr, primitives, braking, vibrationId); + public long composePwle(RampSegment[] primitives, int braking, long vibrationId, + long stepId) { + return performPwleEffect(mNativePtr, primitives, braking, vibrationId, stepId); } /** Turns vibrator on to perform PWLE effect composed of given points. */ - public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) { - return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId); + public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId, long stepId) { + return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId, stepId); } /** Enabled the device vibrator to be controlled by another service. */ diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 75b1b202bcfd..3f5fc338ee3b 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -1293,14 +1293,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } - private void onVibrationComplete(int vibratorId, long vibrationId) { + private void onVibrationComplete(int vibratorId, long vibrationId, long stepId) { synchronized (mLock) { if (mCurrentSession != null) { if (DEBUG) { - Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId - + " complete, notifying thread"); + Slog.d(TAG, "Vibration " + vibrationId + " step " + stepId + + " on vibrator " + vibratorId + " complete, notifying thread"); } - mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId); + mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId, stepId); } } } @@ -2100,10 +2100,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override - public void onComplete(int vibratorId, long vibrationId) { + public void onComplete(int vibratorId, long vibrationId, long stepId) { VibratorManagerService service = mServiceRef.get(); if (service != null) { - service.onVibrationComplete(vibratorId, vibrationId); + service.onVibrationComplete(vibratorId, vibrationId, stepId); } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9692b69b1256..7b6d408fbe2c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -109,7 +109,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED; import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING; -import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_UNSET; import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManager.hasWindowExtensionsEnabled; @@ -321,9 +320,7 @@ import android.util.MergedConfiguration; import android.util.Slog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; -import android.view.AppTransitionAnimationSpec; import android.view.DisplayInfo; -import android.view.IAppTransitionAnimationSpecsFuture; import android.view.InputApplicationHandle; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; @@ -493,6 +490,7 @@ final class ActivityRecord extends WindowToken { private long createTime = System.currentTimeMillis(); long lastVisibleTime; // last time this activity became visible long pauseTime; // last time we started pausing the activity + long mStoppedTime; // last time we completely stopped the activity long launchTickTime; // base time for launch tick messages long topResumedStateLossTime; // last time we reported top resumed state loss to an activity // Last configuration reported to the activity in the client process. @@ -2348,7 +2346,8 @@ final class ActivityRecord extends WindowToken { // The snapshot of home is only used once because it won't be updated while screen // is on (see {@link TaskSnapshotController#screenTurningOff}). mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId); - if ((mDisplayContent.mAppTransition.getTransitFlags() + final Transition transition = mTransitionController.getCollectingTransition(); + if (transition != null && (transition.getFlags() & WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) { // Only use snapshot of home as starting window when unlocking directly. return false; @@ -3637,7 +3636,6 @@ final class ActivityRecord extends WindowToken { if (DEBUG_VISIBILITY || DEBUG_TRANSITION) { Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this); } - mDisplayContent.prepareAppTransition(TRANSIT_CLOSE); // When finishing the activity preemptively take the snapshot before the app window // is marked as hidden and any configuration changes take place @@ -3739,7 +3737,6 @@ final class ActivityRecord extends WindowToken { private void prepareActivityHideTransitionAnimation() { final DisplayContent dc = mDisplayContent; - dc.prepareAppTransition(TRANSIT_CLOSE); setVisibility(false); dc.executeAppTransition(); } @@ -4391,13 +4388,6 @@ final class ActivityRecord extends WindowToken { removeStartingWindow(); } - // If app transition animation was running for this activity, then we need to ensure that - // the app transition notifies that animations have completed in - // DisplayContent.handleAnimatingStoppedAndTransition(), so add to that list now - if (isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) { - getDisplayContent().mNoAnimationNotifyOnTransitionFinished.add(token); - } - if (delayed && !isEmpty()) { // set the token aside because it has an active animation to be finished ProtoLog.v(WM_DEBUG_ADD_REMOVE, @@ -5069,8 +5059,6 @@ final class ActivityRecord extends WindowToken { void applyOptionsAnimation() { if (DEBUG_TRANSITION) Slog.i(TAG, "Applying options for " + this); if (mPendingRemoteAnimation != null) { - mDisplayContent.mAppTransition.overridePendingAppTransitionRemote( - mPendingRemoteAnimation); mTransitionController.setStatusBarTransitionDelay( mPendingRemoteAnimation.getStatusBarTransitionDelay()); } else { @@ -5100,14 +5088,6 @@ final class ActivityRecord extends WindowToken { IRemoteCallback finishCallback = null; switch (animationType) { case ANIM_CUSTOM: - displayContent.mAppTransition.overridePendingAppTransition( - pendingOptions.getPackageName(), - pendingOptions.getCustomEnterResId(), - pendingOptions.getCustomExitResId(), - pendingOptions.getCustomBackgroundColor(), - pendingOptions.getAnimationStartedListener(), - pendingOptions.getAnimationFinishedListener(), - pendingOptions.getOverrideTaskTransition()); options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(), pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(), pendingOptions.getCustomBackgroundColor(), @@ -5116,9 +5096,6 @@ final class ActivityRecord extends WindowToken { finishCallback = pendingOptions.getAnimationFinishedListener(); break; case ANIM_CLIP_REVEAL: - displayContent.mAppTransition.overridePendingAppTransitionClipReveal( - pendingOptions.getStartX(), pendingOptions.getStartY(), - pendingOptions.getWidth(), pendingOptions.getHeight()); options = AnimationOptions.makeClipRevealAnimOptions( pendingOptions.getStartX(), pendingOptions.getStartY(), pendingOptions.getWidth(), pendingOptions.getHeight()); @@ -5130,9 +5107,6 @@ final class ActivityRecord extends WindowToken { } break; case ANIM_SCALE_UP: - displayContent.mAppTransition.overridePendingAppTransitionScaleUp( - pendingOptions.getStartX(), pendingOptions.getStartY(), - pendingOptions.getWidth(), pendingOptions.getHeight()); options = AnimationOptions.makeScaleUpAnimOptions( pendingOptions.getStartX(), pendingOptions.getStartY(), pendingOptions.getWidth(), pendingOptions.getHeight(), @@ -5148,10 +5122,6 @@ final class ActivityRecord extends WindowToken { case ANIM_THUMBNAIL_SCALE_DOWN: final boolean scaleUp = (animationType == ANIM_THUMBNAIL_SCALE_UP); final HardwareBuffer buffer = pendingOptions.getThumbnail(); - displayContent.mAppTransition.overridePendingAppTransitionThumb(buffer, - pendingOptions.getStartX(), pendingOptions.getStartY(), - pendingOptions.getAnimationStartedListener(), - scaleUp); options = AnimationOptions.makeThumbnailAnimOptions(buffer, pendingOptions.getStartX(), pendingOptions.getStartY(), scaleUp); startCallback = pendingOptions.getAnimationStartedListener(); @@ -5164,36 +5134,9 @@ final class ActivityRecord extends WindowToken { break; case ANIM_THUMBNAIL_ASPECT_SCALE_UP: case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN: - final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs(); - final IAppTransitionAnimationSpecsFuture specsFuture = - pendingOptions.getSpecsFuture(); - if (specsFuture != null) { - displayContent.mAppTransition.overridePendingAppTransitionMultiThumbFuture( - specsFuture, pendingOptions.getAnimationStartedListener(), - animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP); - } else if (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_DOWN - && specs != null) { - displayContent.mAppTransition.overridePendingAppTransitionMultiThumb( - specs, pendingOptions.getAnimationStartedListener(), - pendingOptions.getAnimationFinishedListener(), false); - } else { - displayContent.mAppTransition.overridePendingAppTransitionAspectScaledThumb( - pendingOptions.getThumbnail(), - pendingOptions.getStartX(), pendingOptions.getStartY(), - pendingOptions.getWidth(), pendingOptions.getHeight(), - pendingOptions.getAnimationStartedListener(), - (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP)); - if (intent.getSourceBounds() == null) { - intent.setSourceBounds(new Rect(pendingOptions.getStartX(), - pendingOptions.getStartY(), - pendingOptions.getStartX() + pendingOptions.getWidth(), - pendingOptions.getStartY() + pendingOptions.getHeight())); - } - } + // TODO(b/397847511): remove the related types from ActivityOptions. break; case ANIM_OPEN_CROSS_PROFILE_APPS: - displayContent.mAppTransition - .overridePendingAppTransitionStartCrossProfileApps(); options = AnimationOptions.makeCrossProfileAnimOptions(); break; case ANIM_NONE: @@ -5450,8 +5393,6 @@ final class ActivityRecord extends WindowToken { } private void setVisibility(boolean visible, boolean deferHidingClient) { - final AppTransition appTransition = getDisplayContent().mAppTransition; - // Don't set visibility to false if we were already not visible. This prevents WM from // adding the app to the closing app list which doesn't make sense for something that is // already not visible. However, set visibility to true even if we are already visible. @@ -5471,8 +5412,8 @@ final class ActivityRecord extends WindowToken { } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s", - token, visible, appTransition, isVisible(), mVisibleRequested, + "setAppVisibility(%s, visible=%b): visible=%b mVisibleRequested=%b Callers=%s", + token, visible, isVisible(), mVisibleRequested, Debug.getCallers(6)); // Before setting mVisibleRequested so we can track changes. @@ -5569,15 +5510,6 @@ final class ActivityRecord extends WindowToken { updateReportedVisibilityLocked(); } - @Override - boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter, - boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) { - if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { - return false; - } - return super.applyAnimation(lp, transit, enter, isVoiceInteraction, sources); - } - /** * Update visibility to this {@link ActivityRecord}. * @@ -6447,6 +6379,7 @@ final class ActivityRecord extends WindowToken { Slog.w(TAG, "Exception thrown during pause", e); // Just in case, assume it to be stopped. mAppStopped = true; + mStoppedTime = SystemClock.uptimeMillis(); ProtoLog.v(WM_DEBUG_STATES, "Stop failed; moving to STOPPED: %s", this); setState(STOPPED, "stopIfPossible"); } @@ -6480,6 +6413,7 @@ final class ActivityRecord extends WindowToken { if (isStopping) { ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPED: %s (stop complete)", this); + mStoppedTime = SystemClock.uptimeMillis(); setState(STOPPED, "activityStopped"); } @@ -6639,9 +6573,7 @@ final class ActivityRecord extends WindowToken { // starting window is drawn, the transition can start earlier. Exclude finishing and bubble // because it may be a trampoline. if (app == null && !finishing && !mLaunchedFromBubble - && mVisibleRequested && !mDisplayContent.mAppTransition.isReady() - && !mDisplayContent.mAppTransition.isRunning() - && mDisplayContent.isNextTransitionForward()) { + && mVisibleRequested && mDisplayContent.isNextTransitionForward()) { // The pending transition state will be cleared after the transition is started, so // save the state for launching the client later (used by LaunchActivityItem). mStartingData.mIsTransitionForward = true; @@ -7523,7 +7455,6 @@ final class ActivityRecord extends WindowToken { } } - getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token); scheduleAnimation(); // Schedule to handle the stopping and finishing activities which the animation is done diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 37783781a901..6f83822ee97a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2130,6 +2130,26 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) { + enforceTaskPermission("setTaskIsPerceptible()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final Task task = mRootWindowContainer.anyTaskForId(taskId, + MATCH_ATTACHED_TASK_ONLY); + if (task == null) { + Slog.w(TAG, "setTaskIsPerceptible: No task to set with id=" + taskId); + return false; + } + task.mIsPerceptible = isPerceptible; + } + return true; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public boolean removeTask(int taskId) { mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()"); synchronized (mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java index 15c0789d777e..3535a96d9c45 100644 --- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java @@ -36,8 +36,7 @@ import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; - -import com.android.window.flags.Flags; +import android.window.DesktopModeFlags; /** * Encapsulate app compat policy logic related to aspect ratio. @@ -298,7 +297,8 @@ class AppCompatAspectRatioPolicy { // Camera compat mode is an exception to this, where the activity is letterboxed // to an aspect ratio commonly found on phones, e.g. 16:9, to avoid issues like // stretching of the camera preview. - || (Flags.ignoreAspectRatioRestrictionsForResizeableFreeformActivities() + || (DesktopModeFlags + .IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES.isTrue() && task.getWindowingMode() == WINDOWING_MODE_FREEFORM && !mActivityRecord.shouldCreateAppCompatDisplayInsets() && !AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio( diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index b932ef362aca..6718ae435cd9 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -748,14 +748,9 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { && policy.okToAnimate(true /* ignoreScreenOn */)) { return false; } - // Consider unoccluding only when all unknown visibilities have been - // resolved, as otherwise we just may be starting another occluding activity. - final boolean isUnoccluding = - mDisplayContent.mAppTransition.isUnoccluding() - && mDisplayContent.mUnknownAppVisibilityController.allResolved(); - // If keyguard is showing, or we're unoccluding, force the keyguard's orientation, + // Use keyguard's orientation if it is showing and not occluded // even if SystemUI hasn't updated the attrs yet. - if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) { + if (policy.isKeyguardShowingAndNotOccluded()) { return true; } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e30c24d87d20..214a3a15bb44 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -88,7 +88,6 @@ import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; import static android.window.DisplayAreaOrganizer.FEATURE_IME; import static android.window.DisplayAreaOrganizer.FEATURE_ROOT; -import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONTENT_RECORDING; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS; @@ -369,12 +368,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private MetricsLogger mMetricsLogger; - /** - * List of clients without a transtiton animation that we notify once we are done - * transitioning since they won't be notified through the app window animator. - */ - final List<IBinder> mNoAnimationNotifyOnTransitionFinished = new ArrayList<>(); - // Mapping from a token IBinder to a WindowToken object on this display. private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap(); @@ -829,28 +822,36 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> { final ActivityRecord focusedApp = mFocusedApp; + final boolean canReceiveKeys = w.canReceiveKeys(); ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s", - w, w.mAttrs.flags, w.canReceiveKeys(), + w, w.mAttrs.flags, canReceiveKeys, w.canReceiveKeysReason(false /* fromUserTouch */)); - if (!w.canReceiveKeys()) { + if (!canReceiveKeys) { return false; } - // When switching the app task, we keep the IME window visibility for better - // transitioning experiences. - // However, in case IME created a child window or the IME selection dialog without - // dismissing during the task switching to keep the window focus because IME window has - // higher window hierarchy, we don't give it focus if the next IME layering target - // doesn't request IME visible. - if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null - || !mImeLayeringTarget.isRequestedVisible(ime()))) { - return false; - } - if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null - && !(mImeLayeringTarget.isRequestedVisible(ime()) - && mImeLayeringTarget.isVisibleRequested())) { - return false; + // IME windows remain visibleRequested while switching apps to maintain a smooth animation. + // This persists until the new app is focused, so they can be visibleRequested despite not + // being visible to the user (i.e. occluded). These rank higher in the window hierarchy than + // app windows, so they will always be considered first. To avoid having the focus stuck, + // an IME window (child or not) cannot be focused if the IME parent is not visible. However, + // child windows also require the IME to be visible in the current app. + if (w.mIsImWindow) { + final boolean imeParentVisible = mInputMethodSurfaceParentWindow != null + && mInputMethodSurfaceParentWindow.isVisibleRequested(); + if (!imeParentVisible) { + ProtoLog.v(WM_DEBUG_FOCUS, "findFocusedWindow: IME window not focusable as" + + " IME parent is not visible"); + return false; + } + + if (w.isChildWindow() + && !getInsetsStateController().getImeSourceProvider().isImeShowing()) { + ProtoLog.v(WM_DEBUG_FOCUS, "findFocusedWindow: IME child window not focusable as" + + " IME is not visible"); + return false; + } } final ActivityRecord activity = w.mActivityRecord; @@ -1171,8 +1172,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mFixedRotationTransitionListener = new FixedRotationTransitionListener(mDisplayId); mAppTransition = new AppTransition(mWmService.mContext, mWmService, this); - mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier); - mAppTransition.registerListenerLocked(mFixedRotationTransitionListener); mTransitionController.registerLegacyListener(mFixedRotationTransitionListener); mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); mRemoteDisplayChangeController = new RemoteDisplayChangeController(this); @@ -2858,13 +2857,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return isVisible() && !mRemoved && !mRemoving; } - @Override - void onAppTransitionDone() { - super.onAppTransitionDone(); - mWmService.mWindowsChanged = true; - onTransitionFinished(); - } - void onTransitionFinished() { // If the transition finished callback cannot match the token for some reason, make sure the // rotated state is cleared if it is already invisible. @@ -3385,9 +3377,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDeferredRemoval = false; try { mUnknownAppVisibilityController.clear(); - mAppTransition.removeAppTransitionTimeoutCallbacks(); mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener); - handleAnimatingStoppedAndTransition(); mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer); super.removeImmediately(); if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this); @@ -3570,11 +3560,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayRotation.dumpDebug(proto, DISPLAY_ROTATION); mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES); proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp); - if (mTransitionController.isShellTransitionsEnabled()) { - mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION); - } else { - mAppTransition.dumpDebug(proto, APP_TRANSITION); - } + mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION); if (mFocusedApp != null) { mFocusedApp.writeNameToProto(proto, FOCUSED_APP); } @@ -5634,61 +5620,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * @see AppTransition#prepareAppTransition */ void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit, - @WindowManager.TransitionFlags int flags) { - prepareAppTransition(transit, flags); - mTransitionController.requestTransitionIfNeeded(transit, flags, null /* trigger */, this); + @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) { + mTransitionController.requestTransitionIfNeeded(transit, flags, trigger, this); } void executeAppTransition() { mTransitionController.setReady(this); - if (mAppTransition.isTransitionSet()) { - ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, - "Execute app transition: %s, displayId: %d Callers=%s", - mAppTransition, mDisplayId, Debug.getCallers(5)); - mAppTransition.setReady(); - mWmService.mWindowPlacerLocked.requestTraversal(); - } - } - - /** - * Update pendingLayoutChanges after app transition has finished. - */ - void handleAnimatingStoppedAndTransition() { - int changes = 0; - - mAppTransition.setIdle(); - - for (int i = mNoAnimationNotifyOnTransitionFinished.size() - 1; i >= 0; i--) { - final IBinder token = mNoAnimationNotifyOnTransitionFinished.get(i); - mAppTransition.notifyAppTransitionFinishedLocked(token); - } - mNoAnimationNotifyOnTransitionFinished.clear(); - - mWallpaperController.hideDeferredWallpapersIfNeededLegacy(); - - onAppTransitionDone(); - - changes |= FINISH_LAYOUT_REDO_LAYOUT; - ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout"); - computeImeTarget(true /* updateImeTarget */); - mWallpaperMayChange = true; - // Since the window list has been rebuilt, focus might have to be recomputed since the - // actual order of windows might have changed again. - mWmService.mFocusMayChange = true; - - pendingLayoutChanges |= changes; } /** Check if pending app transition is for activity / task launch. */ boolean isNextTransitionForward() { // TODO(b/191375840): decouple "forwardness" from transition system. - if (mTransitionController.isShellTransitionsEnabled()) { - @WindowManager.TransitionType int type = - mTransitionController.getCollectingTransitionType(); - return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT; - } - return mAppTransition.containsTransitRequest(TRANSIT_OPEN) - || mAppTransition.containsTransitRequest(TRANSIT_TO_FRONT); + final @WindowManager.TransitionType int type = + mTransitionController.getCollectingTransitionType(); + return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT; } /** diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index fbe850198c50..7aa2101f516c 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -631,7 +631,6 @@ public class DisplayPolicy { mHandler.post(mAppTransitionFinished); } }; - displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener); displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener); // TODO: Make it can take screenshot on external display diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 6091b8334438..6d73739e5046 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -250,7 +250,7 @@ class KeyguardController { // to the locked state before holding the sleep token again if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { dc.requestTransitionAndLegacyPrepare( - TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING); + TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null); } dc.mWallpaperController.adjustWallpaperWindows(); dc.executeAppTransition(); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 40f16c187f20..f309372ab6a2 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2103,10 +2103,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED); } - // Set a transition to ensure that we don't immediately try and update the visibility - // of the activity entering PIP - r.getDisplayContent().prepareAppTransition(TRANSIT_NONE); - transitionController.collect(task); // Defer the windowing mode change until after the transition to prevent the activity diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 6b3499a5d68c..6cd1336122be 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -54,7 +54,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; -import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -504,6 +503,17 @@ class Task extends TaskFragment { int mOffsetYForInsets; /** + * When set to true, the task will be kept at a PERCEPTIBLE_APP_ADJ, and downgraded + * to PREVIOUS_APP_ADJ if not in foreground for a period of time. + * One example use case is for desktop form factors, where it is important keep tasks in the + * perceptible state (rather than cached where it may be frozen) when a user moves it to the + * foreground. + * On startup, restored Tasks will not be perceptible, until user actually interacts with it + * (i.e. brings it to the foreground) + */ + boolean mIsPerceptible = false; + + /** * Whether the compatibility overrides that change the resizability of the app should be allowed * for the specific app. */ @@ -1647,8 +1657,7 @@ class Task extends TaskFragment { // Prevent the transition from being executed too early if the top activity is // resumed but the mVisibleRequested of any other activity is true, the transition // should wait until next activity resumed. - if (r.isState(RESUMED) || (r.isVisible() - && !mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CLOSE))) { + if (r.isState(RESUMED) || r.isVisible()) { r.finishIfPossible(reason, false /* oomAdj */); } else { r.destroyIfPossible(reason); @@ -2324,11 +2333,6 @@ class Task extends TaskFragment { mLastSurfaceSize.set(width, height); } - @VisibleForTesting - boolean isInChangeTransition() { - return AppTransition.isChangeTransitOld(mTransit); - } - @Override void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); @@ -3854,6 +3858,7 @@ class Task extends TaskFragment { pw.print(ActivityInfo.resizeModeToString(mResizeMode)); pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture); pw.print(" isResizeable="); pw.println(isResizeable()); + pw.print(" isPerceptible="); pw.println(mIsPerceptible); pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime); pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)"); pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents); @@ -5293,11 +5298,9 @@ class Task extends TaskFragment { // Place a new activity at top of root task, so it is next to interact with the user. if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mDisplayContent.prepareAppTransition(TRANSIT_NONE); mTaskSupervisor.mNoAnimActivities.add(r); mTransitionController.setNoAnimation(r); } else { - mDisplayContent.prepareAppTransition(TRANSIT_OPEN); mTaskSupervisor.mNoAnimActivities.remove(r); } if (newTask && !r.mLaunchTaskBehind) { @@ -5477,7 +5480,8 @@ class Task extends TaskFragment { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); Task finishedTask = r.getTask(); - mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED); + mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED, + finishedTask); r.finishIfPossible(reason, false /* oomAdj */); // Also terminate any activities below it that aren't yet stopped, to avoid a situation @@ -5695,7 +5699,6 @@ class Task extends TaskFragment { ActivityOptions.abort(options); } } - mDisplayContent.prepareAppTransition(transit); } final void moveTaskToFront(Task tr, boolean noAnimation, ActivityOptions options, @@ -5747,12 +5750,9 @@ class Task extends TaskFragment { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr); if (noAnimation) { - mDisplayContent.prepareAppTransition(TRANSIT_NONE); mTaskSupervisor.mNoAnimActivities.add(top); - if (mTransitionController.isShellTransitionsEnabled()) { - mTransitionController.collect(top); - mTransitionController.setNoAnimation(top); - } + mTransitionController.collect(top); + mTransitionController.setNoAnimation(top); ActivityOptions.abort(options); } else { updateTransitLocked(TRANSIT_TO_FRONT, options); @@ -5862,10 +5862,6 @@ class Task extends TaskFragment { moveTaskToBackInner(tr, transition); }); } else { - // Skip the transition for pinned task. - if (!inPinnedWindowingMode()) { - mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK); - } moveTaskToBackInner(tr, null /* transition */); } return true; diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index bbda849262b2..80095b395cc4 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -44,10 +44,6 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; -import static android.view.WindowManager.TRANSIT_NONE; -import static android.view.WindowManager.TRANSIT_OPEN; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES; import static com.android.server.wm.ActivityRecord.State.PAUSED; @@ -56,7 +52,6 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityRecord.State.STOPPING; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION; @@ -1682,36 +1677,15 @@ class TaskFragment extends WindowContainer<WindowContainer> { final DisplayContent dc = taskDisplayArea.mDisplayContent; if (prev != null) { if (prev.finishing) { - if (DEBUG_TRANSITION) { - Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev); - } if (mTaskSupervisor.mNoAnimActivities.contains(prev)) { anim = false; - dc.prepareAppTransition(TRANSIT_NONE); - } else { - dc.prepareAppTransition(TRANSIT_CLOSE); } prev.setVisibility(false); - } else { - if (DEBUG_TRANSITION) { - Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev); - } - if (mTaskSupervisor.mNoAnimActivities.contains(next)) { - anim = false; - dc.prepareAppTransition(TRANSIT_NONE); - } else { - dc.prepareAppTransition(TRANSIT_OPEN, - next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0); - } - } - } else { - if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous"); - if (mTaskSupervisor.mNoAnimActivities.contains(next)) { + } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) { anim = false; - dc.prepareAppTransition(TRANSIT_NONE); - } else { - dc.prepareAppTransition(TRANSIT_OPEN); } + } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) { + anim = false; } if (anim) { @@ -1915,10 +1889,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { // Even if the transient activity is occluded, defer pausing (addToStopping will still // be called) it until the transient transition is done. So the current resuming // activity won't need to wait for additional pause complete. + ProtoLog.d(WM_DEBUG_STATES, "startPausing: Skipping pause for transient " + + "resumed activity=%s", mResumedActivity); return false; } - ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this, + ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag=%s mResumedActivity=%s", this, mResumedActivity); if (mPausingActivity != null) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 5217a759c6ae..fd7d96a646ae 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1640,6 +1640,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST); } mSyncEngine.addToSyncSet(syncId, target); + } else { + // If there is an existing sync group for the commit-at-end activity, + // enforce BLAST sync method for its windows, before resuming config dispatch. + target.forAllWindows(windowState -> { + windowState.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; + }, true /* traverseTopToBottom */); } // Reset surface state here (since it was skipped in buildFinishTransaction). Since // we are resuming config to the "current" state, we have to calculate the matching @@ -2026,23 +2032,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mOverrideOptions == null) { return; } - - if (!Flags.moveAnimationOptionsToChange()) { - info.setAnimationOptions(mOverrideOptions); - } else { - final List<TransitionInfo.Change> changes = info.getChanges(); - for (int i = changes.size() - 1; i >= 0; --i) { - final WindowContainer<?> container = mTargets.get(i).mContainer; - if (container.asActivityRecord() != null - || shouldApplyAnimOptionsToTask(container.asTask())) { - changes.get(i).setAnimationOptions(mOverrideOptions); - // TODO(b/295805497): Extract mBackgroundColor from AnimationOptions. - changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor()); - } else if (shouldApplyAnimOptionsToEmbeddedTf(container.asTaskFragment())) { - // We only override AnimationOptions because backgroundColor should be from - // TaskFragmentAnimationParams. - changes.get(i).setAnimationOptions(mOverrideOptions); - } + final List<TransitionInfo.Change> changes = info.getChanges(); + for (int i = changes.size() - 1; i >= 0; --i) { + final WindowContainer<?> container = mTargets.get(i).mContainer; + if (container.asActivityRecord() != null + || shouldApplyAnimOptionsToTask(container.asTask())) { + changes.get(i).setAnimationOptions(mOverrideOptions); + // TODO(b/295805497): Extract mBackgroundColor from AnimationOptions. + changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor()); + } else if (shouldApplyAnimOptionsToEmbeddedTf(container.asTaskFragment())) { + // We only override AnimationOptions because backgroundColor should be from + // TaskFragmentAnimationParams. + changes.get(i).setAnimationOptions(mOverrideOptions); } } updateActivityTargetForCrossProfileAnimation(info); @@ -2933,9 +2934,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final AnimationOptions animOptionsForActivityTransition = calculateAnimationOptionsForActivityTransition(type, sortedTargets); - if (!Flags.moveAnimationOptionsToChange() && animOptionsForActivityTransition != null) { - out.setAnimationOptions(animOptionsForActivityTransition); - } final ArraySet<WindowContainer> occludedAtEndContainers = new ArraySet<>(); // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order. @@ -3059,28 +3057,26 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } AnimationOptions animOptions = null; - if (Flags.moveAnimationOptionsToChange()) { - if (activityRecord != null && animOptionsForActivityTransition != null) { - animOptions = animOptionsForActivityTransition; - } else if (Flags.activityEmbeddingOverlayPresentationFlag() - && isEmbeddedTaskFragment) { - final TaskFragmentAnimationParams params = taskFragment.getAnimationParams(); - if (params.hasOverrideAnimation()) { - // Only set AnimationOptions if there's any animation override. - // We use separated field for backgroundColor, and - // AnimationOptions#backgroundColor will be removed in long term. - animOptions = AnimationOptions.makeCustomAnimOptions( - taskFragment.getTask().getBasePackageName(), - params.getOpenAnimationResId(), params.getChangeAnimationResId(), - params.getCloseAnimationResId(), 0 /* backgroundColor */, - false /* overrideTaskTransition */); - animOptions.setUserId(taskFragment.getTask().mUserId); - } - } - if (animOptions != null) { - change.setAnimationOptions(animOptions); + if (activityRecord != null && animOptionsForActivityTransition != null) { + animOptions = animOptionsForActivityTransition; + } else if (Flags.activityEmbeddingOverlayPresentationFlag() + && isEmbeddedTaskFragment) { + final TaskFragmentAnimationParams params = taskFragment.getAnimationParams(); + if (params.hasOverrideAnimation()) { + // Only set AnimationOptions if there's any animation override. + // We use separated field for backgroundColor, and + // AnimationOptions#backgroundColor will be removed in long term. + animOptions = AnimationOptions.makeCustomAnimOptions( + taskFragment.getTask().getBasePackageName(), + params.getOpenAnimationResId(), params.getChangeAnimationResId(), + params.getCloseAnimationResId(), 0 /* backgroundColor */, + false /* overrideTaskTransition */); + animOptions.setUserId(taskFragment.getTask().mUserId); } } + if (animOptions != null) { + change.setAnimationOptions(animOptions); + } if (activityRecord != null) { change.setActivityComponent(activityRecord.mActivityComponent); diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java index 242883612124..e612d8ec0ce6 100644 --- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java +++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java @@ -20,6 +20,7 @@ import static android.graphics.Matrix.MSCALE_X; import static android.graphics.Matrix.MSCALE_Y; import static android.graphics.Matrix.MSKEW_X; import static android.graphics.Matrix.MSKEW_Y; +import static android.view.Display.INVALID_DISPLAY; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TPL; @@ -35,6 +36,7 @@ import android.util.ArrayMap; import android.util.IntArray; import android.util.Pair; import android.util.Size; +import android.util.SparseArray; import android.view.InputWindowHandle; import android.window.ITrustedPresentationListener; import android.window.TrustedPresentationThresholds; @@ -251,7 +253,7 @@ public class TrustedPresentationListenerController { Rect tmpLogicalDisplaySize = new Rect(); Matrix tmpInverseMatrix = new Matrix(); float[] tmpMatrix = new float[9]; - Region coveredRegionsAbove = new Region(); + SparseArray<Region> coveredRegionsAboveByDisplay = new SparseArray<>(); long currTimeMs = System.currentTimeMillis(); ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.first.length); @@ -262,7 +264,7 @@ public class TrustedPresentationListenerController { ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name); continue; } - var displayFound = false; + int displayId = INVALID_DISPLAY; tmpRectF.set(windowHandle.frame); for (var displayHandle : mLastWindowHandles.second) { if (displayHandle.mDisplayId == windowHandle.displayId) { @@ -273,17 +275,18 @@ public class TrustedPresentationListenerController { tmpLogicalDisplaySize.set(0, 0, displayHandle.mLogicalSize.getWidth(), displayHandle.mLogicalSize.getHeight()); tmpRect.intersect(tmpLogicalDisplaySize); - displayFound = true; + displayId = displayHandle.mDisplayId; break; } } - if (!displayFound) { + if (displayId == INVALID_DISPLAY) { ProtoLog.v(WM_DEBUG_TPL, "Skipping %s, no associated display %d", windowHandle.name, windowHandle.displayId); continue; } + Region coveredRegionsAbove = coveredRegionsAboveByDisplay.get(displayId, new Region()); var listeners = mRegisteredListeners.get(windowHandle.getWindowToken()); if (listeners != null) { Region region = new Region(); @@ -304,6 +307,7 @@ public class TrustedPresentationListenerController { } coveredRegionsAbove.op(tmpRect, Region.Op.UNION); + coveredRegionsAboveByDisplay.put(displayId, coveredRegionsAbove); ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s", windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove); } diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 7c88abcec7ec..9506ffeb2792 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -217,8 +217,7 @@ class WallpaperWindowToken extends WindowToken { } // If in a transition, defer commits for activities that are going invisible - if (!visible && (mTransitionController.inTransition() - || getDisplayContent().mAppTransition.isRunning())) { + if (!visible && mTransitionController.inTransition()) { return; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 45202a29ba97..d0d2067ac4bc 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -16,9 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -29,24 +26,15 @@ import static android.content.pm.ActivityInfo.reverseOrientation; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.os.UserHandle.USER_NULL; import static android.view.SurfaceControl.Transaction; import static android.view.WindowInsets.Type.InsetsType; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; -import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM; -import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS; -import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SYNC_ENGINE; -import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION; -import static com.android.server.wm.AppTransition.isActivityTransitOld; -import static com.android.server.wm.AppTransition.isTaskFragmentTransitOld; -import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; @@ -65,7 +53,6 @@ import static com.android.server.wm.WindowContainerProto.VISIBLE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM; import android.annotation.CallSuper; import android.annotation.ColorInt; @@ -81,10 +68,8 @@ import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; -import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.Pair; import android.util.Pools; import android.util.RotationUtils; import android.util.Slog; @@ -102,14 +87,11 @@ import android.view.SurfaceControl.Builder; import android.view.SurfaceControlViewHost; import android.view.WindowManager; import android.view.WindowManager.TransitionOldType; -import android.view.animation.Animation; import android.window.IWindowContainerToken; import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.graphics.ColorUtils; import com.android.internal.protolog.ProtoLog; -import com.android.internal.protolog.common.LogLevel; import com.android.internal.util.ToBooleanFunction; import com.android.server.wm.SurfaceAnimator.Animatable; import com.android.server.wm.SurfaceAnimator.AnimationType; @@ -1315,31 +1297,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** - * Returns true if the container or one of its children as some content it can display or wants - * to display (e.g. app views or saved surface). - * - * NOTE: While this method will return true if the there is some content to display, it doesn't - * mean the container is visible. Use {@link #isVisible()} to determine if the container is - * visible. - */ - boolean hasContentToDisplay() { - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowContainer wc = mChildren.get(i); - if (wc.hasContentToDisplay()) { - return true; - } - } - return false; - } - - /** * Returns true if the container or one of its children is considered visible from the * WindowManager perspective which usually means valid surface and some other internal state * are true. * * NOTE: While this method will return true if the surface is visible, it doesn't mean the - * client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if - * the container has any content to display. + * client has actually displayed any content. */ boolean isVisible() { // TODO: Will this be more correct if it checks the visibility of its parents? @@ -1480,13 +1443,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - void onAppTransitionDone() { - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowContainer wc = mChildren.get(i); - wc.onAppTransitionDone(); - } - } - /** * Called when this container or one of its descendants changed its requested orientation, and * wants this container to handle it or pass it to its parent. @@ -3039,264 +2995,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< getRelativePosition(outPosition); } - /** - * Applies the app transition animation according the given the layout properties in the - * window hierarchy. - * - * @param lp The layout parameters of the window. - * @param transit The app transition type indicates what kind of transition to be applied. - * @param enter Whether the app transition is entering transition or not. - * @param isVoiceInteraction Whether the container is participating in voice interaction or not. - * @param sources {@link ActivityRecord}s which causes this app transition animation. - * - * @return {@code true} when the container applied the app transition, {@code false} if the - * app transition is disabled or skipped. - * - * @see #getAnimationAdapter - */ - boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit, - boolean enter, boolean isVoiceInteraction, - @Nullable ArrayList<WindowContainer> sources) { - if (mWmService.mDisableTransitionAnimation) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, - "applyAnimation: transition animation is disabled or skipped. " - + "container=%s", this); - cancelAnimation(); - return false; - } - - // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason - // to animate and it can cause strange artifacts when we unfreeze the display if some - // different animation is running. - try { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation"); - if (okToAnimate()) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, - "applyAnimation: transit=%s, enter=%b, wc=%s", - AppTransition.appTransitionOldToString(transit), enter, this); - applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources); - } else { - cancelAnimation(); - } - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - - return isAnimating(); - } - - /** - * Gets the {@link AnimationAdapter} according the given window layout properties in the window - * hierarchy. - * - * @return The return value will always contain two elements, one for normal animations and the - * other for thumbnail animation, both can be {@code null}. - * - * @See com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord - * @See LocalAnimationAdapter - */ - Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp, - @TransitionOldType int transit, boolean enter, boolean isVoiceInteraction) { - final Pair<AnimationAdapter, AnimationAdapter> resultAdapters; - final int appRootTaskClipMode = getDisplayContent().mAppTransition.getAppRootTaskClipMode(); - - // Separate position and size for use in animators. - final Rect screenBounds = getAnimationBounds(appRootTaskClipMode); - mTmpRect.set(screenBounds); - getAnimationPosition(mTmpPoint); - mTmpRect.offsetTo(0, 0); - - final boolean isChanging = AppTransition.isChangeTransitOld(transit); - - if (isChanging) { - final float durationScale = mWmService.getTransitionAnimationScaleLocked(); - final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo(); - mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y); - - final AnimationAdapter adapter = new LocalAnimationAdapter( - new WindowChangeAnimationSpec(null /* startBounds */, mTmpRect, - displayInfo, durationScale, true /* isAppAnimation */, - false /* isThumbnail */), - getSurfaceAnimationRunner()); - - final AnimationAdapter thumbnailAdapter = null; - resultAdapters = new Pair<>(adapter, thumbnailAdapter); - mTransit = transit; - mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags(); - } else { - mNeedsAnimationBoundsLayer = (appRootTaskClipMode == ROOT_TASK_CLIP_AFTER_ANIM); - final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction); - - if (a != null) { - // Only apply corner radius to animation if we're not in multi window mode. - // We don't want rounded corners when in pip or split screen. - final float windowCornerRadius = !inMultiWindowMode() - ? getDisplayContent().getWindowCornerRadius() - : 0; - if (asActivityRecord() != null - && asActivityRecord().isNeedsLetterboxedAnimation()) { - asActivityRecord().getLetterboxInnerBounds(mTmpRect); - } - AnimationAdapter adapter = new LocalAnimationAdapter( - new WindowAnimationSpec(a, mTmpPoint, mTmpRect, - getDisplayContent().mAppTransition.canSkipFirstFrame(), - appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius), - getSurfaceAnimationRunner()); - - resultAdapters = new Pair<>(adapter, null); - mNeedsZBoost = a.getZAdjustment() == Animation.ZORDER_TOP - || AppTransition.isClosingTransitOld(transit); - mTransit = transit; - mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags(); - } else { - resultAdapters = new Pair<>(null, null); - } - } - return resultAdapters; - } - - protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, - @TransitionOldType int transit, boolean isVoiceInteraction, - @Nullable ArrayList<WindowContainer> sources) { - final Task task = asTask(); - if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) { - final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); - final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null - && imeTarget.getWindow().getTask() == task; - // Attach and show the IME screenshot when the task is the IME target and performing - // task closing transition to the next task. - if (isImeLayeringTarget && AppTransition.isTaskCloseTransitOld(transit)) { - mDisplayContent.showImeScreenshot(); - } - } - final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, - transit, enter, isVoiceInteraction); - AnimationAdapter adapter = adapters.first; - AnimationAdapter thumbnailAdapter = adapters.second; - if (adapter != null) { - if (sources != null) { - mSurfaceAnimationSources.addAll(sources); - } - - AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder(); - - // Check if the animation requests to show background color for Activity and embedded - // TaskFragment. - final ActivityRecord activityRecord = asActivityRecord(); - final TaskFragment taskFragment = asTaskFragment(); - if (adapter.getShowBackground() - // Check if it is Activity transition. - && ((activityRecord != null && isActivityTransitOld(transit)) - // Check if it is embedded TaskFragment transition. - || (taskFragment != null && taskFragment.isEmbedded() - && isTaskFragmentTransitOld(transit)))) { - final @ColorInt int backgroundColorForTransition; - if (adapter.getBackgroundColor() != 0) { - // If available use the background color provided through getBackgroundColor - // which if set originates from a call to overridePendingAppTransition. - backgroundColorForTransition = adapter.getBackgroundColor(); - } else { - final TaskFragment organizedTf = activityRecord != null - ? activityRecord.getOrganizedTaskFragment() - : taskFragment.getOrganizedTaskFragment(); - if (organizedTf != null && organizedTf.getAnimationParams() - .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) { - // This window is embedded and has an animation background color set on the - // TaskFragment. Pass this color with this window, so the handler can use it - // as the animation background color if needed, - backgroundColorForTransition = organizedTf.getAnimationParams() - .getAnimationBackgroundColor(); - } else { - // Otherwise default to the window's background color if provided through - // the theme as the background color for the animation - the top most window - // with a valid background color and showBackground set takes precedence. - final Task parentTask = activityRecord != null - ? activityRecord.getTask() - : taskFragment.getTask(); - backgroundColorForTransition = parentTask.getTaskDescription() - .getBackgroundColor(); - } - } - // Set to opaque for animation background to prevent it from exposing the blank - // background or content below. - animationRunnerBuilder.setTaskBackgroundColor(ColorUtils.setAlphaComponent( - backgroundColorForTransition, 255)); - } - - animationRunnerBuilder.build() - .startAnimation(getPendingTransaction(), adapter, !isVisible(), - ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter); - - if (adapter.getShowWallpaper()) { - getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - } - } - } - final SurfaceAnimationRunner getSurfaceAnimationRunner() { return mWmService.mSurfaceAnimationRunner; } - private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - boolean isVoiceInteraction) { - if ((isOrganized() - // TODO(b/161711458): Clean-up when moved to shell. - && getWindowingMode() != WINDOWING_MODE_FULLSCREEN - && getWindowingMode() != WINDOWING_MODE_FREEFORM - && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) { - return null; - } - - final DisplayContent displayContent = getDisplayContent(); - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - final int width = displayInfo.appWidth; - final int height = displayInfo.appHeight; - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: container=%s", this); - - // Determine the visible rect to calculate the thumbnail clip with - // getAnimationFrames. - final Rect frame = new Rect(0, 0, width, height); - final Rect displayFrame = new Rect(0, 0, - displayInfo.logicalWidth, displayInfo.logicalHeight); - final Rect insets = new Rect(); - final Rect stableInsets = new Rect(); - final Rect surfaceInsets = new Rect(); - getAnimationFrames(frame, insets, stableInsets, surfaceInsets); - - if (mLaunchTaskBehind) { - // Differentiate the two animations. This one which is briefly on the screen - // gets the !enter animation, and the other one which remains on the - // screen gets the enter animation. Both appear in the mOpeningApps set. - enter = false; - } - ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, - "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s " - + "surfaceInsets=%s", - AppTransition.appTransitionOldToString(transit), enter, frame, insets, - surfaceInsets); - final Configuration displayConfig = displayContent.getConfiguration(); - final Animation a = getDisplayContent().mAppTransition.loadAnimation(lp, transit, enter, - displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets, - surfaceInsets, stableInsets, isVoiceInteraction, inFreeformWindowingMode(), this); - if (a != null) { - if (a != null) { - // Setup the maximum app transition duration to prevent malicious app may set a long - // animation duration or infinite repeat counts for the app transition through - // ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition. - a.restrictDuration(MAX_APP_TRANSITION_DURATION); - } - if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) { - ProtoLog.i(WM_DEBUG_ANIM, "Loaded animation %s for %s, duration: %d, stack=%s", - a, this, ((a != null) ? a.getDuration() : 0), Debug.getCallers(20)); - } - final int containingWidth = frame.width(); - final int containingHeight = frame.height(); - a.initialize(containingWidth, containingHeight, width, height); - a.scaleCurrentDuration(mWmService.getTransitionAnimationScaleLocked()); - } - return a; - } - boolean canCreateRemoteAnimationTarget() { return false; } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 30dde543b9d4..270de0197a4e 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -37,6 +37,7 @@ import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityRecord.State.STARTED; import static com.android.server.wm.ActivityRecord.State.STOPPING; +import static com.android.server.wm.ActivityRecord.State.STOPPED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE; @@ -344,6 +345,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio */ private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; + /** + * The most recent timestamp of when one of this process's stopped activities in a + * perceptible task became stopped. Written by window manager and read by activity manager. + */ + private volatile long mPerceptibleTaskStoppedTimeMillis = Long.MIN_VALUE; + public WindowProcessController(@NonNull ActivityTaskManagerService atm, @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner, @NonNull WindowProcessListener listener) { @@ -475,8 +482,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio r.detachFromProcess(); if (r.isVisibleRequested()) { hasVisibleActivity = true; + Task finishingTask = r.getTask(); r.mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, - TRANSIT_FLAG_APP_CRASHED); + TRANSIT_FLAG_APP_CRASHED, finishingTask); } r.destroyIfPossible("handleAppCrashed"); } @@ -1228,6 +1236,17 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mActivityStateFlags; } + /** + * Returns the most recent timestamp when one of this process's stopped activities in a + * perceptible task became stopped. It should only be called if {@link #hasActivities} + * returns {@code true} and {@link #getActivityStateFlags} does not have any of + * the ACTIVITY_STATE_FLAG_IS_(VISIBLE|PAUSING_OR_PAUSED|STOPPING) bit set. + */ + @HotPath(caller = HotPath.OOM_ADJUSTMENT) + public long getPerceptibleTaskStoppedTimeMillis() { + return mPerceptibleTaskStoppedTimeMillis; + } + void computeProcessActivityState() { // Since there could be more than one activities in a process record, we don't need to // compute the OomAdj with each of them, just need to find out the activity with the @@ -1239,6 +1258,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio int minTaskLayer = Integer.MAX_VALUE; int stateFlags = 0; int nonOccludedRatio = 0; + long perceptibleTaskStoppedTimeMillis = Long.MIN_VALUE; final boolean wasResumed = hasResumedActivity(); final boolean wasAnyVisible = (mActivityStateFlags & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; @@ -1287,6 +1307,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio bestInvisibleState = STOPPING; // Not "finishing" if any of activity isn't finishing. allStoppingFinishing &= r.finishing; + } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) { + if (task.mIsPerceptible) { + perceptibleTaskStoppedTimeMillis = + Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis); + } } } } @@ -1324,6 +1349,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } mActivityStateFlags = stateFlags; + mPerceptibleTaskStoppedTimeMillis = perceptibleTaskStoppedTimeMillis; final boolean anyVisible = (stateFlags & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 92ad2cec364b..bfedc90497ae 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1698,17 +1698,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP || mActivityRecord.isStartingWindowDisplayed()); } - @Override - boolean hasContentToDisplay() { - if (!isDrawn() && (mViewVisibility == View.VISIBLE - || (isAnimating(TRANSITION | PARENTS) - && !getDisplayContent().mAppTransition.isTransitionSet()))) { - return true; - } - - return super.hasContentToDisplay(); - } - private boolean isVisibleByPolicyOrInsets() { return isVisibleByPolicy() // If we don't have a provider, this window isn't used as a window generating diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index d26539c377a9..95776088aad8 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -204,6 +204,7 @@ cc_defaults { "android.frameworks.sensorservice-V1-ndk", "android.frameworks.stats@1.0", "android.frameworks.stats-V2-ndk", + "android.os.vibrator.flags-aconfig-cc", "android.system.suspend.control-V1-cpp", "android.system.suspend.control.internal-cpp", "android.system.suspend-V1-ndk", diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index abd4cd25cf68..534dbb1f6cf1 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -21,6 +21,7 @@ #include <android/binder_parcel_jni.h> #include <android/hardware/vibrator/1.3/IVibrator.h> #include <android/persistable_bundle_aidl.h> +#include <android_os_vibrator.h> #include <nativehelper/JNIHelp.h> #include <utils/Log.h> #include <utils/misc.h> @@ -143,21 +144,23 @@ public: return mHal->doWithRetry(fn, functionName); } - std::function<void()> createCallback(jlong vibrationId) { + std::function<void()> createCallback(jlong vibrationId, jlong stepId) { auto callbackId = ++mCallbackId; - return [vibrationId, callbackId, this]() { + return [vibrationId, stepId, callbackId, this]() { auto currentCallbackId = mCallbackId.load(); - if (currentCallbackId != callbackId) { - // This callback is from an older HAL call that is no longer relevant to the service + if (!android_os_vibrator_fix_vibration_thread_callback_handling() && + currentCallbackId != callbackId) { + // This callback is from an older HAL call that is no longer relevant return; } auto jniEnv = GetOrAttachJNIEnvironment(sJvm); - jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, mVibratorId, - vibrationId); + jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, mVibratorId, vibrationId, + stepId); }; } void disableOldCallbacks() { + // TODO remove this once android_os_vibrator_fix_vibration_thread_callback_handling removed mCallbackId++; } @@ -165,6 +168,7 @@ private: const std::shared_ptr<vibrator::HalController> mHal; const int32_t mVibratorId; const jobject mCallbackListener; + // TODO remove this once android_os_vibrator_fix_vibration_thread_callback_handling removed std::atomic<int64_t> mCallbackId; }; @@ -273,13 +277,13 @@ static jboolean vibratorIsAvailable(JNIEnv* env, jclass /* clazz */, jlong ptr) } static jlong vibratorOn(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong timeoutMs, - jlong vibrationId) { + jlong vibrationId, jlong stepId) { VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); if (wrapper == nullptr) { ALOGE("vibratorOn failed because native wrapper was not initialized"); return -1; } - auto callback = wrapper->createCallback(vibrationId); + auto callback = wrapper->createCallback(vibrationId, stepId); auto onFn = [timeoutMs, &callback](vibrator::HalWrapper* hal) { return hal->on(std::chrono::milliseconds(timeoutMs), callback); }; @@ -324,7 +328,7 @@ static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong pt } static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong effect, - jlong strength, jlong vibrationId) { + jlong strength, jlong vibrationId, jlong stepId) { VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); if (wrapper == nullptr) { ALOGE("vibratorPerformEffect failed because native wrapper was not initialized"); @@ -332,7 +336,7 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, j } Aidl::Effect effectType = static_cast<Aidl::Effect>(effect); Aidl::EffectStrength effectStrength = static_cast<Aidl::EffectStrength>(strength); - auto callback = wrapper->createCallback(vibrationId); + auto callback = wrapper->createCallback(vibrationId, stepId); auto performEffectFn = [effectType, effectStrength, &callback](vibrator::HalWrapper* hal) { return hal->performEffect(effectType, effectStrength, callback); }; @@ -342,7 +346,7 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, j static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject vendorData, jlong strength, jfloat scale, - jfloat adaptiveScale, jlong vibrationId) { + jfloat adaptiveScale, jlong vibrationId, jlong stepId) { VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); if (wrapper == nullptr) { ALOGE("vibratorPerformVendorEffect failed because native wrapper was not initialized"); @@ -350,7 +354,7 @@ static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong } Aidl::VendorEffect effect = vendorEffectFromJavaParcel(env, vendorData, strength, scale, adaptiveScale); - auto callback = wrapper->createCallback(vibrationId); + auto callback = wrapper->createCallback(vibrationId, stepId); auto performVendorEffectFn = [&effect, &callback](vibrator::HalWrapper* hal) { return hal->performVendorEffect(effect, callback); }; @@ -359,7 +363,8 @@ static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong } static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, - jobjectArray composition, jlong vibrationId) { + jobjectArray composition, jlong vibrationId, + jlong stepId) { VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); if (wrapper == nullptr) { ALOGE("vibratorPerformComposedEffect failed because native wrapper was not initialized"); @@ -371,7 +376,7 @@ static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlon jobject element = env->GetObjectArrayElement(composition, i); effects.push_back(effectFromJavaPrimitive(env, element)); } - auto callback = wrapper->createCallback(vibrationId); + auto callback = wrapper->createCallback(vibrationId, stepId); auto performComposedEffectFn = [&effects, &callback](vibrator::HalWrapper* hal) { return hal->performComposedEffect(effects, callback); }; @@ -381,7 +386,8 @@ static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlon } static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, - jobjectArray waveform, jint brakingId, jlong vibrationId) { + jobjectArray waveform, jint brakingId, jlong vibrationId, + jlong stepId) { VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); if (wrapper == nullptr) { ALOGE("vibratorPerformPwleEffect failed because native wrapper was not initialized"); @@ -406,7 +412,7 @@ static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong pt } } - auto callback = wrapper->createCallback(vibrationId); + auto callback = wrapper->createCallback(vibrationId, stepId); auto performPwleEffectFn = [&primitives, &callback](vibrator::HalWrapper* hal) { return hal->performPwleEffect(primitives, callback); }; @@ -415,7 +421,7 @@ static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong pt } static jlong vibratorPerformPwleV2Effect(JNIEnv* env, jclass /* clazz */, jlong ptr, - jobjectArray waveform, jlong vibrationId) { + jobjectArray waveform, jlong vibrationId, jlong stepId) { VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); if (wrapper == nullptr) { ALOGE("vibratorPerformPwleV2Effect failed because native wrapper was not initialized"); @@ -431,7 +437,7 @@ static jlong vibratorPerformPwleV2Effect(JNIEnv* env, jclass /* clazz */, jlong } composite.pwlePrimitives = primitives; - auto callback = wrapper->createCallback(vibrationId); + auto callback = wrapper->createCallback(vibrationId, stepId); auto composePwleV2Fn = [&composite, &callback](vibrator::HalWrapper* hal) { return hal->composePwleV2(composite, callback); }; @@ -610,16 +616,16 @@ static const JNINativeMethod method_table[] = { (void*)vibratorNativeInit}, {"getNativeFinalizer", "()J", (void*)vibratorGetNativeFinalizer}, {"isAvailable", "(J)Z", (void*)vibratorIsAvailable}, - {"on", "(JJJ)J", (void*)vibratorOn}, + {"on", "(JJJJ)J", (void*)vibratorOn}, {"off", "(J)V", (void*)vibratorOff}, {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude}, - {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect}, - {"performVendorEffect", "(JLandroid/os/Parcel;JFFJ)J", (void*)vibratorPerformVendorEffect}, - {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J", + {"performEffect", "(JJJJJ)J", (void*)vibratorPerformEffect}, + {"performVendorEffect", "(JLandroid/os/Parcel;JFFJJ)J", (void*)vibratorPerformVendorEffect}, + {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;JJ)J", (void*)vibratorPerformComposedEffect}, - {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J", + {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJJ)J", (void*)vibratorPerformPwleEffect}, - {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;J)J", + {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;JJ)J", (void*)vibratorPerformPwleV2Effect}, {"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl}, {"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable}, @@ -632,7 +638,7 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env auto listenerClassName = "com/android/server/vibrator/VibratorController$OnVibrationCompleteListener"; jclass listenerClass = FindClassOrDie(env, listenerClassName); - sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJ)V"); + sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJJ)V"); jclass primitiveClass = FindClassOrDie(env, "android/os/vibrator/PrimitiveSegment"); sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "mPrimitiveId", "I"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 5b7e7f17c454..e3b9fdb16d59 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -1454,11 +1454,12 @@ final class DevicePolicyEngine { } for (int i = 0; i < mLocalPolicies.size(); i++) { - Set<PolicyKey> localPolicies = new HashSet<>( - mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet()); + // New users are potentially added to mLocalPolicies during the loop body + // (see b/374511959). + int userId = mLocalPolicies.keyAt(i); + Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet()); for (PolicyKey policy : localPolicies) { - PolicyState<?> policyState = mLocalPolicies.get( - mLocalPolicies.keyAt(i)).get(policy); + PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy); if (policyState.getPoliciesSetByAdmins().containsKey(admin)) { removeLocalPolicy( policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i)); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 191c21e661d0..aee32a0473a3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -423,6 +423,7 @@ import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Bitmap; import android.hardware.usb.UsbManager; +import android.health.connect.HealthConnectManager; import android.location.Location; import android.location.LocationManager; import android.media.AudioManager; @@ -2149,6 +2150,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); mBackgroundHandler = BackgroundThread.getHandler(); + // Add the health permission to the list of restricted permissions. + if (android.permission.flags.Flags.replaceBodySensorPermissionEnabled()) { + Set<String> healthPermissions = HealthConnectManager.getHealthPermissions(mContext); + for (String permission : healthPermissions) { + SENSOR_PERMISSIONS.add(permission); + } + } + // Needed when mHasFeature == false, because it controls the certificate warning text. mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java index ae5e85163e9a..856466675a28 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java @@ -40,10 +40,14 @@ import android.content.res.Configuration; import android.graphics.Insets; import android.os.Build; import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.server.wm.WindowManagerStateHelper; import android.util.Log; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; import android.view.WindowManagerGlobal; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.Flags; @@ -72,6 +76,7 @@ import org.junit.Test; import org.junit.rules.TestName; import org.junit.runner.RunWith; +import java.util.List; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -139,10 +144,9 @@ public class InputMethodServiceTest { if (!mOriginalVerboseImeTrackerLoggingEnabled) { setVerboseImeTrackerLogging(true); } + mUiDevice.setOrientationNatural(); prepareIme(); prepareActivity(); - mUiDevice.freezeRotation(); - mUiDevice.setOrientationNatural(); // Waits for input binding ready. eventually(() -> { mInputMethodService = InputMethodServiceWrapper.getInstance(); @@ -169,6 +173,9 @@ public class InputMethodServiceTest { @After public void tearDown() throws Exception { + if (!mUiDevice.isNaturalOrientation()) { + mUiDevice.setOrientationNatural(); + } mUiDevice.unfreezeRotation(); if (!mOriginalVerboseImeTrackerLoggingEnabled) { setVerboseImeTrackerLogging(false); @@ -245,6 +252,61 @@ public class InputMethodServiceTest { } /** + * This checks that the surface is removed after the window was hidden in + * InputMethodService#hideSoftInput + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER) + public void testSurfaceRemovedAfterHideSoftInput() { + setShowImeWithHardKeyboard(true /* enabled */); + + // Triggers to show IME via public API. + verifyInputViewStatusOnMainSync(() -> mActivity.showImeWithWindowInsetsController(), + EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown"); + assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue(); + + final var window = mInputMethodService.getWindow().getWindow(); + assertWithMessage("IME window exists").that(window).isNotNull(); + assertWithMessage("IME window showing").that( + window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE); + + mActivity.getWindow().getDecorView().setWindowInsetsAnimationCallback( + new WindowInsetsAnimation.Callback( + WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) { + @NonNull + @Override + public WindowInsetsAnimation.Bounds onStart( + @NonNull WindowInsetsAnimation animation, + @NonNull WindowInsetsAnimation.Bounds bounds) { + return super.onStart(animation, bounds); + } + + @NonNull + @Override + public WindowInsets onProgress(@NonNull WindowInsets insets, + @NonNull List<WindowInsetsAnimation> runningAnimations) { + assertWithMessage("IME surface not removed during the animation").that( + window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE); + return insets; + } + + @Override + public void onEnd(@NonNull WindowInsetsAnimation animation) { + assertWithMessage( + "IME surface not removed before the end of the animation").that( + window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE); + super.onEnd(animation); + } + }); + + // Triggers to hide IME via public API. + verifyInputViewStatusOnMainSync(() -> mActivity.hideImeWithWindowInsetsController(), + EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown"); + eventually(() -> assertWithMessage("IME window not showing").that( + window.getDecorView().getVisibility()).isEqualTo(View.GONE)); + } + + /** * This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf. */ @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index e0e44252a8f3..c151732cec66 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -984,8 +984,7 @@ public class DisplayManagerServiceTest { Handler handler = displayManager.getDisplayHandler(); waitForIdleHandler(handler); - assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED, - EVENT_DISPLAY_REFRESH_RATE_CHANGED); + assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED); } /** diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 1ef758cf192e..340115a7d465 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -3337,6 +3337,108 @@ public class MockingOomAdjusterTests { followUpTimeCaptor.capture()); } + /** + * For Perceptible Tasks adjustment, this solely unit-tests OomAdjuster -> onOtherActivity() + */ + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS) + public void testPerceptibleAdjustment() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + + long now = mInjector.getUptimeMillis(); + + // GIVEN: perceptible adjustment is NOT enabled (perceptible stop time is not set) + // EXPECT: zero adjustment + // TLDR: App is not set as a perceptible task and hence no oom_adj boosting. + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ, + false, false, PROCESS_STATE_CACHED_ACTIVITY, + SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND); + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(-1); + assertEquals(CACHED_APP_MIN_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj); + + // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and + // elapsed time < PERCEPTIBLE_TASK_TIMEOUT + // EXPECT: adjustment to PERCEPTIBLE_MEDIUM_APP_ADJ + // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting. + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ, + false, false, PROCESS_STATE_CACHED_ACTIVITY, + SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND); + mInjector.reset(); + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(now); + assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ, + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj); + + // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and + // elapsed time > PERCEPTIBLE_TASK_TIMEOUT + // EXPECT: adjustment to PREVIOUS_APP_ADJ + // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting, but + // time has elapsed and has dropped to a lower boosting of PREVIOUS_APP_ADJ + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ, + false, false, PROCESS_STATE_CACHED_ACTIVITY, + SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND); + mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 1000); + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(0); + assertEquals(PREVIOUS_APP_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj); + } + + /** + * For Perceptible Tasks adjustment, this tests overall adjustment flow. + */ + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS) + public void testUpdateOomAdjPerceptible() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + WindowProcessController wpc = app.getWindowProcessController(); + + // Set uptime to be at least the timeout time + buffer, so that we don't end up with + // negative stopTime in our test input + mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 60L * 1000L); + long now = mInjector.getUptimeMillis(); + doReturn(true).when(wpc).hasActivities(); + + // GIVEN: perceptible adjustment is is enabled + // EXPECT: perceptible-act adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) + .when(wpc).getActivityStateFlags(); + doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_IMPORTANT_BACKGROUND, PERCEPTIBLE_MEDIUM_APP_ADJ, + SCHED_GROUP_BACKGROUND, "perceptible-act"); + + // GIVEN: perceptible adjustment is is enabled and timeout has been reached + // EXPECT: stale-perceptible-act adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) + .when(wpc).getActivityStateFlags(); + + doReturn(now - OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS).when( + wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ, + SCHED_GROUP_BACKGROUND, "stale-perceptible-act"); + + // GIVEN: perceptible adjustment is is disabled + // EXPECT: no perceptible adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) + .when(wpc).getActivityStateFlags(); + doReturn(Long.MIN_VALUE).when(wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_CACHED_ACTIVITY, CACHED_APP_MIN_ADJ, + SCHED_GROUP_BACKGROUND, "cch-act"); + + // GIVEN: perceptible app is in foreground + // EXPECT: no perceptible adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE) + .when(wpc).getActivityStateFlags(); + doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, + SCHED_GROUP_DEFAULT, "vis-activity"); + } + @SuppressWarnings("GuardedBy") @Test public void testUpdateOomAdj_DoAll_Multiple_Provider_Retention() { 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 bc04fd94c719..49f2e9d70287 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -258,6 +258,8 @@ public class WallpaperManagerServiceTests { spyOn(mIpm); spyOn(mResources); doReturn(true).when(mResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)); + doReturn(true).when(mResources).getBoolean( + eq(R.bool.config_canInternalDisplayHostDesktops)); mService = new TestWallpaperManagerService(sContext); spyOn(mService); mService.systemReady(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java index 0e73329dcfe5..978fd1af2636 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java @@ -18,6 +18,7 @@ package com.android.server.power.stats.processor; import static com.google.common.truth.Truth.assertThat; +import android.annotation.SuppressLint; import android.os.BatteryConsumer; import android.os.PersistableBundle; import android.util.SparseArray; @@ -280,6 +281,7 @@ public class AggregatedPowerStatsTest { .isEqualTo(new long[]{250, 300}); } + @SuppressLint("CheckResult") private static long[] getDeviceStats( AggregatedPowerStats stats, int powerComponentId, int... states) { @@ -290,6 +292,7 @@ public class AggregatedPowerStatsTest { return out; } + @SuppressLint("CheckResult") private static long[] getStateStats( AggregatedPowerStats stats, int key, int... states) { PowerComponentAggregatedPowerStats powerComponentStats = @@ -299,6 +302,7 @@ public class AggregatedPowerStatsTest { return out; } + @SuppressLint("CheckResult") private static long[] getUidDeviceStats( AggregatedPowerStats stats, int powerComponentId, int uid, int... states) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java index 21e615f8c740..58e9d1e26f2c 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java @@ -27,6 +27,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; import android.os.Handler; @@ -166,6 +167,7 @@ public class AmbientDisplayPowerStatsProcessorTest { return stats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY); } + @SuppressLint("CheckResult") private void assertPowerEstimate( PowerComponentAggregatedPowerStats aggregatedStats, int powerState, int screenState, double expectedPowerEstimate) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java index cca60339acf7..58784d7b4a25 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java @@ -34,6 +34,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; +import android.annotation.SuppressLint; import android.os.BatteryConsumer; import android.os.BatteryUsageStats; import android.os.Process; @@ -68,6 +69,7 @@ public class BasePowerStatsProcessorTest { .setProcessorSupplier(() -> new BasePowerStatsProcessor(() -> 4000)); } + @SuppressLint("CheckResult") @Test public void processPowerStats() { AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(true); @@ -95,9 +97,11 @@ public class BasePowerStatsProcessorTest { stats.getUidStats(uidStats, APP_UID1, states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(5000); - stats.getUidStats(uidStats, APP_UID1, + boolean nonZero = stats.getUidStats(uidStats, APP_UID1, states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_UNSPECIFIED)); - assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0); + if (nonZero) { + assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0); + } stats.getUidStats(uidStats, APP_UID2, states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); @@ -105,11 +109,14 @@ public class BasePowerStatsProcessorTest { stats.getUidStats(uidStats, APP_UID2, states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(8500); - stats.getUidStats(uidStats, APP_UID2, + nonZero = stats.getUidStats(uidStats, APP_UID2, states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_UNSPECIFIED)); - assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0); + if (nonZero) { + assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0); + } } + @SuppressLint("CheckResult") @Test public void fuelgaugeAvailable() { AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(true); @@ -138,6 +145,7 @@ public class BasePowerStatsProcessorTest { assertThat(dischargeDuration).isWithin(5).of(6000); } + @SuppressLint("CheckResult") @Test public void fuelgaugeUnavailable() { AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(false); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java index 2ff7dde3255f..e6e7f6e105b7 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java @@ -30,6 +30,7 @@ import static com.android.server.power.stats.processor.AggregatedPowerStatsConfi import static com.google.common.truth.Truth.assertThat; +import android.annotation.SuppressLint; import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.PersistableBundle; @@ -74,6 +75,7 @@ public class BinaryStatePowerStatsProcessorTest { } } + @SuppressLint("CheckResult") @Test public void powerProfileModel() { BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout(); @@ -138,12 +140,14 @@ public class BinaryStatePowerStatsProcessorTest { assertThat(statsLayout.getUidPowerEstimate(uidStats)) .isWithin(PRECISION).of(expectedPower2); - stats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0); + if (stats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) { + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0); + } } + @SuppressLint("CheckResult") @Test public void energyConsumerModel() { BinaryStatePowerStatsLayout @@ -232,10 +236,11 @@ public class BinaryStatePowerStatsProcessorTest { assertThat(statsLayout.getUidPowerEstimate(uidStats)) .isWithin(PRECISION).of(expectedPower2); - stats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0); + if (stats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) { + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0); + } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java index 60131861ce89..6d7119dc1f0e 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java @@ -33,6 +33,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothAdapter; import android.bluetooth.UidTraffic; @@ -161,6 +162,7 @@ public class BluetoothPowerStatsProcessorTest { when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)).thenReturn(true); } + @SuppressLint("CheckResult") @Test public void powerProfileModel_mostlyDataTransfer() { // No power monitoring hardware @@ -262,6 +264,7 @@ public class BluetoothPowerStatsProcessorTest { .isWithin(PRECISION).of(expectedPower2 * 0.75); } + @SuppressLint("CheckResult") @Test public void powerProfileModel_mostlyScan() { // No power monitoring hardware @@ -361,6 +364,7 @@ public class BluetoothPowerStatsProcessorTest { .isWithin(PRECISION).of(expectedPower2 * 0.75); } + @SuppressLint("CheckResult") @Test public void consumedEnergyModel() { when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java index 23642de466a1..a95963242d8f 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java @@ -33,6 +33,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; @@ -117,6 +118,7 @@ public class CameraPowerStatsTest { mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock()); } + @SuppressLint("CheckResult") @Test public void energyConsumerModel() { when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV); @@ -211,10 +213,11 @@ public class CameraPowerStatsTest { assertThat(statsLayout.getUidPowerEstimate(uidStats)) .isWithin(PRECISION).of(expectedPower2); - stats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0); + if (stats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) { + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0); + } } private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn, diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java index 42baba765276..a421675f1896 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java @@ -33,6 +33,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.hardware.power.stats.EnergyConsumerAttribution; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; @@ -154,6 +155,7 @@ public class CustomEnergyConsumerPowerStatsTest { .isEqualTo(6000); } + @SuppressLint("CheckResult") @Test public void processStats() throws Exception { AggregatedPowerStats aggregatedPowerStats = createAggregatedPowerStats(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java index c63267ec6ae7..b4f21133f621 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java @@ -33,6 +33,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; import android.location.GnssSignalQuality; @@ -122,6 +123,7 @@ public class GnssPowerStatsTest { mHistoryItem.clear(); } + @SuppressLint("CheckResult") @Test public void powerProfileModel() { // ODPM unsupported @@ -206,12 +208,14 @@ public class GnssPowerStatsTest { assertThat(statsLayout.getUidPowerEstimate(uidStats)) .isWithin(PRECISION).of(0.51111); - stats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0); + if (stats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) { + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0); + } } + @SuppressLint("CheckResult") @Test public void initialStateGnssOn() { // ODPM unsupported @@ -285,12 +289,14 @@ public class GnssPowerStatsTest { assertThat(statsLayout.getUidPowerEstimate(uidStats)) .isWithin(PRECISION).of(0.51111); - stats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0); + if (stats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) { + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0); + } } + @SuppressLint("CheckResult") @Test public void energyConsumerModel() { when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV); @@ -386,10 +392,11 @@ public class GnssPowerStatsTest { assertThat(statsLayout.getUidPowerEstimate(uidStats)) .isWithin(PRECISION).of(expectedPower2); - stats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0); + if (stats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) { + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0); + } } private BatteryStats.HistoryItem buildHistoryItemInitialStateGpsOn(long timestamp) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java index 6acd36885b1e..3dc401769e7d 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.power.stats.EnergyConsumerResult; @@ -170,6 +171,7 @@ public class MobileRadioPowerStatsProcessorTest { .thenAnswer(invocation -> invocation.getArgument(0)); } + @SuppressLint("CheckResult") @Test public void powerProfileModel() { // No power monitoring hardware diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java index 3b614bdb38ed..9abb06f338e3 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; +import android.annotation.SuppressLint; import android.os.BatteryConsumer; import androidx.test.filters.SmallTest; @@ -124,6 +125,7 @@ public class MultiStateStatsTest { assertThat(e.getMessage()).contains("40"); } + @SuppressLint("CheckResult") @Test public void multiStateStats_aggregation() { MultiStateStats.Factory factory = makeFactory(true, true, false); @@ -159,9 +161,9 @@ public class MultiStateStatsTest { // (400 - 100) * 0 + (600 - 400) * 0.5 assertThat(stats).isEqualTo(new long[]{100, 100}); - multiStateStats.getStats(stats, new int[]{1, BatteryConsumer.PROCESS_STATE_BACKGROUND, 0}); // Never been in this composite state - assertThat(stats).isEqualTo(new long[]{0, 0}); + assertThat(multiStateStats.getStats(stats, + new int[]{1, BatteryConsumer.PROCESS_STATE_BACKGROUND, 0})).isFalse(); } @Test diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java index a20274fb5ded..2f742d74d8e6 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.power.stats.EnergyConsumerType; @@ -159,6 +160,7 @@ public class PhoneCallPowerStatsProcessorTest { mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem"); } + @SuppressLint("CheckResult") @Test public void copyEstimatesFromMobileRadioPowerStats() { AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java index 185216583f1b..31456a1574d0 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; @@ -278,6 +279,7 @@ public class ScreenPowerStatsProcessorTest { .of(expectedDozePowerEstimate); } + @SuppressLint("CheckResult") private void assertUidPowerEstimate( PowerComponentAggregatedPowerStats aggregatedStats, int uid, int powerState, int screenState, double expectedScreenPowerEstimate) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java index d97260455bdd..c2f01d1fa65c 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java @@ -33,6 +33,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.input.InputSensorInfo; @@ -91,6 +92,7 @@ public class SensorPowerStatsProcessorTest { List.of(sensor1, sensor2, sensor3)); } + @SuppressLint("CheckResult") @Test public void testPowerEstimation() { PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats( diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java index 8257d714a5d5..5ac7216194a4 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java @@ -31,6 +31,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; +import android.annotation.SuppressLint; import android.os.BatteryConsumer; import android.os.PersistableBundle; import android.os.Process; @@ -123,6 +124,7 @@ public class WakelockPowerStatsProcessorTest { return history; } + @SuppressLint("CheckResult") private void assertAggregatedPowerStats(AggregatedPowerStats aggregatedPowerStats) { PowerComponentAggregatedPowerStats stats = aggregatedPowerStats.getPowerComponentStats(POWER_COMPONENT_WAKELOCK); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java index bd92a84fc815..e36056a98c85 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.power.stats.EnergyConsumerResult; @@ -198,6 +199,7 @@ public class WifiPowerStatsProcessorTest { mBatteryStats = mStatsRule.getBatteryStats(); } + @SuppressLint("CheckResult") @Test public void powerProfileModel_powerController() { when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(true); @@ -310,6 +312,7 @@ public class WifiPowerStatsProcessorTest { .isWithin(PRECISION).of(expectedPower2 * 0.75); } + @SuppressLint("CheckResult") @Test public void consumedEnergyModel_powerController() { when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(true); @@ -424,6 +427,7 @@ public class WifiPowerStatsProcessorTest { .isWithin(PRECISION).of(expectedPower2 * 0.75); } + @SuppressLint("CheckResult") @Test public void powerProfileModel_noPowerController() { when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(false); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index d702cae248a9..009ce88cfd6c 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -33,6 +33,9 @@ android_test { "test-apps/DisplayManagerTestApp/src/**/*.java", ], + kotlincflags: [ + "-Werror", + ], static_libs: [ "a11ychecker", "aatf", diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java index 7f60caaa569b..0745c68fd337 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.platform.test.annotations.DisableFlags; @@ -400,6 +401,46 @@ public class AutoclickControllerTest { .isNotEqualTo(initialScheduledTime); } + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void pauseButton_flagOn_clickNotTriggeredWhenPaused() { + injectFakeMouseActionHoverMoveEvent(); + + // Pause autoclick. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isPaused()).thenReturn(true); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + + // Verify there is not a pending click. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + + // Resume autoclick. + when(mockAutoclickTypePanel.isPaused()).thenReturn(false); + + // Send initial move event again. Because this is the first recorded move, a click won't be + // scheduled. + injectFakeMouseActionHoverMoveEvent(); + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + + // Send move again to trigger click and verify there is now a pending click. + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1); + } + private void injectFakeMouseActionHoverMoveEvent() { MotionEvent event = getFakeMotionHoverMoveEvent(); event.setSource(InputDevice.SOURCE_MOUSE); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java index f3016f4b17f9..c60c4b60d081 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java @@ -21,6 +21,9 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType; +import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_BOTTOM_LEFT; +import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_BOTTOM_RIGHT; +import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_TOP_RIGHT; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface; import static com.google.common.truth.Truth.assertThat; @@ -31,6 +34,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.LinearLayout; @@ -68,6 +72,7 @@ public class AutoclickTypePanelTest { private LinearLayout mPositionButton; private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK; + private boolean mPaused; private final ClickPanelControllerInterface clickPanelController = new ClickPanelControllerInterface() { @@ -77,7 +82,9 @@ public class AutoclickTypePanelTest { } @Override - public void toggleAutoclickPause() {} + public void toggleAutoclickPause(boolean paused) { + mPaused = paused; + } }; @Before @@ -199,6 +206,116 @@ public class AutoclickTypePanelTest { } } + @Test + public void pauseButton_onClick() { + mPauseButton.callOnClick(); + assertThat(mPaused).isTrue(); + assertThat(mAutoclickTypePanel.isPaused()).isTrue(); + + mPauseButton.callOnClick(); + assertThat(mPaused).isFalse(); + assertThat(mAutoclickTypePanel.isPaused()).isFalse(); + } + + @Test + public void onTouch_dragMove_updatesPosition() { + View contentView = mAutoclickTypePanel.getContentViewForTesting(); + WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting(); + int[] panelLocation = new int[2]; + contentView.getLocationOnScreen(panelLocation); + + // Define movement delta for both x and y directions. + int delta = 15; + + // Dispatch initial down event. + float touchX = panelLocation[0] + 10; + float touchY = panelLocation[1] + 10; + MotionEvent downEvent = MotionEvent.obtain( + 0, 0, + MotionEvent.ACTION_DOWN, touchX, touchY, 0); + contentView.dispatchTouchEvent(downEvent); + + // Create move event with delta, move from (x, y) to (x + delta, y + delta) + MotionEvent moveEvent = MotionEvent.obtain( + 0, 0, + MotionEvent.ACTION_MOVE, touchX + delta, touchY + delta, 0); + contentView.dispatchTouchEvent(moveEvent); + + // Verify position update. + assertThat(mAutoclickTypePanel.getIsDraggingForTesting()).isTrue(); + assertThat(params.gravity).isEqualTo(Gravity.LEFT | Gravity.TOP); + assertThat(params.x).isEqualTo(panelLocation[0] + delta); + assertThat(params.y).isEqualTo(panelLocation[1] + delta); + } + + @Test + public void dragAndEndAtRight_snapsToRightSide() { + View contentView = mAutoclickTypePanel.getContentViewForTesting(); + WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting(); + int[] panelLocation = new int[2]; + contentView.getLocationOnScreen(panelLocation); + + int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels; + + // Verify initial corner is bottom-right. + assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()) + .isEqualTo(CORNER_BOTTOM_RIGHT); + + dispatchDragSequence(contentView, + /* startX =*/ panelLocation[0] + 10, /* startY =*/ panelLocation[1] + 10, + /* endX =*/ (float) (screenWidth * 3) / 4, /* endY =*/ panelLocation[1] + 10); + + // Verify snapping to the right. + assertThat(params.gravity).isEqualTo(Gravity.END | Gravity.TOP); + assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()) + .isEqualTo(CORNER_TOP_RIGHT); + } + + @Test + public void dragAndEndAtLeft_snapsToLeftSide() { + View contentView = mAutoclickTypePanel.getContentViewForTesting(); + WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting(); + int[] panelLocation = new int[2]; + contentView.getLocationOnScreen(panelLocation); + + int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels; + + // Verify initial corner is bottom-right. + assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()) + .isEqualTo(CORNER_BOTTOM_RIGHT); + + dispatchDragSequence(contentView, + /* startX =*/ panelLocation[0] + 10, /* startY =*/ panelLocation[1] + 10, + /* endX =*/ (float) screenWidth / 4, /* endY =*/ panelLocation[1] + 10); + + // Verify snapping to the left. + assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP); + assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()) + .isEqualTo(CORNER_BOTTOM_LEFT); + } + + // Helper method to handle drag event sequences + private void dispatchDragSequence(View view, float startX, float startY, float endX, + float endY) { + // Down event + MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, startX, startY, + 0); + view.dispatchTouchEvent(downEvent); + + // Move event + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, endX, endY, 0); + view.dispatchTouchEvent(moveEvent); + + // Up event + MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, endX, endY, 0); + view.dispatchTouchEvent(upEvent); + + // Clean up + downEvent.recycle(); + moveEvent.recycle(); + upEvent.recycle(); + } + private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) { GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground(); assertThat(gradientDrawable.getColor().getDefaultColor()) @@ -206,7 +323,7 @@ public class AutoclickTypePanelTest { } private void verifyPanelPosition(int[] expectedPosition) { - WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParams(); + WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting(); assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo( expectedPosition[0]); assertThat(params.gravity).isEqualTo(expectedPosition[1]); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 9f5dd93054c3..5c126d1f5d3f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -311,8 +311,7 @@ public class FullScreenMagnificationGestureHandlerTest { mMockFullScreenMagnificationVibrationHelper, mMockMagnificationLogger, ViewConfiguration.get(mContext), - mMockOneFingerPanningSettingsProvider, - new MouseEventHandler(mFullScreenMagnificationController)); + mMockOneFingerPanningSettingsProvider); // OverscrollHandler is only supported on watches. // @See config_enable_a11y_fullscreen_magnification_overscroll_handler if (isWatch()) { @@ -482,8 +481,8 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) public void testDisablingTripleTap_removesInputLag() { - mMgh = newInstance(/* detectSingleFingerTripleTap */ false, - /* detectTwoFingerTripleTap */ true, /* detectShortcut */ true); + mMgh = newInstance(/* detectSingleFingerTripleTap= */ false, + /* detectTwoFingerTripleTap= */ true, /* detectShortcutTrigger= */ true); goFromStateIdleTo(STATE_IDLE); allowEventDelegation(); tap(); @@ -494,8 +493,8 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) public void testDisablingSingleFingerTripleTapAndTwoFingerTripleTap_removesInputLag() { - mMgh = newInstance(/* detectSingleFingerTripleTap */ false, - /* detectTwoFingerTripleTap */ false, /* detectShortcut */ true); + mMgh = newInstance(/* detectSingleFingerTripleTap= */ false, + /* detectTwoFingerTripleTap= */ false, /* detectShortcutTrigger= */ true); goFromStateIdleTo(STATE_IDLE); allowEventDelegation(); tap(); @@ -1420,12 +1419,6 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) - public void testMouseMoveEventsDoNotMoveMagnifierViewport() { - runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE); - } - - @Test public void testStylusMoveEventsDoNotMoveMagnifierViewport() { runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS); } @@ -1441,7 +1434,7 @@ public class FullScreenMagnificationGestureHandlerTest { (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f; float scale = 5.6f; // value is unimportant but unique among tests to increase coverage. mFullScreenMagnificationController.setScaleAndCenter( - DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1); + DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1); centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0); centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0); @@ -1474,55 +1467,36 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) - public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() { - runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE); - } - - @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) - public void testStylusHoverMoveEventsDoNotMoveMagnifierViewport() { - runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseHoverMoveEventsMoveMagnifierViewport() { runHoverMovesViewportTest(InputDevice.SOURCE_MOUSE); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testStylusHoverMoveEventsMoveMagnifierViewport() { runHoverMovesViewportTest(InputDevice.SOURCE_STYLUS); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseDownEventsDoNotMoveMagnifierViewport() { runDownDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testStylusDownEventsDoNotMoveMagnifierViewport() { runDownDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseUpEventsDoNotMoveMagnifierViewport() { runUpDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testStylusUpEventsDoNotMoveMagnifierViewport() { runUpDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseMoveEventsMoveMagnifierViewport() { final EventCaptor eventCaptor = new EventCaptor(); mMgh.setNext(eventCaptor); @@ -1533,7 +1507,7 @@ public class FullScreenMagnificationGestureHandlerTest { (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f; float scale = 6.2f; // value is unimportant but unique among tests to increase coverage. mFullScreenMagnificationController.setScaleAndCenter( - DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1); + DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1); MotionEvent event = mouseEvent(centerX, centerY, ACTION_HOVER_MOVE); send(event, InputDevice.SOURCE_MOUSE); fastForward(20); @@ -1574,7 +1548,7 @@ public class FullScreenMagnificationGestureHandlerTest { (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f; float scale = 4.0f; // value is unimportant but unique among tests to increase coverage. mFullScreenMagnificationController.setScaleAndCenter( - DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1); + DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1); // HOVER_MOVE should change magnifier viewport. MotionEvent event = motionEvent(centerX + 20, centerY, ACTION_HOVER_MOVE); @@ -1618,7 +1592,7 @@ public class FullScreenMagnificationGestureHandlerTest { (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f; float scale = 5.3f; // value is unimportant but unique among tests to increase coverage. mFullScreenMagnificationController.setScaleAndCenter( - DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1); + DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1); MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE); send(event, source); fastForward(20); @@ -1652,7 +1626,7 @@ public class FullScreenMagnificationGestureHandlerTest { (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f; float scale = 2.7f; // value is unimportant but unique among tests to increase coverage. mFullScreenMagnificationController.setScaleAndCenter( - DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1); + DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1); MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE); send(event, source); fastForward(20); @@ -1688,7 +1662,7 @@ public class FullScreenMagnificationGestureHandlerTest { (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f; float scale = 3.8f; // value is unimportant but unique among tests to increase coverage. mFullScreenMagnificationController.setScaleAndCenter( - DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1); + DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1); centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0); centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0); @@ -1725,7 +1699,7 @@ public class FullScreenMagnificationGestureHandlerTest { (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f; float scale = 4.0f; // value is unimportant but unique among tests to increase coverage. mFullScreenMagnificationController.setScaleAndCenter( - DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1); + DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1); centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0); centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java index 45c157d1c1a0..203e6554b412 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java @@ -21,15 +21,11 @@ import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_UP; -import static junit.framework.Assert.assertFalse; - import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.testng.AssertJUnit.assertTrue; import android.annotation.NonNull; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; @@ -39,7 +35,6 @@ import android.view.MotionEvent; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityTraceManager; -import com.android.server.accessibility.Flags; import org.junit.Before; import org.junit.Rule; @@ -93,7 +88,6 @@ public class MagnificationGestureHandlerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() { final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); mouseEvent.setSource(InputDevice.SOURCE_MOUSE); @@ -108,7 +102,6 @@ public class MagnificationGestureHandlerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() { final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); stylusEvent.setSource(InputDevice.SOURCE_STYLUS); @@ -123,36 +116,6 @@ public class MagnificationGestureHandlerTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) - public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() { - final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); - mouseEvent.setSource(InputDevice.SOURCE_MOUSE); - - mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0); - - try { - assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled); - } finally { - mouseEvent.recycle(); - } - } - - @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) - public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() { - final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); - stylusEvent.setSource(InputDevice.SOURCE_STYLUS); - - mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0); - - try { - assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled); - } finally { - stylusEvent.recycle(); - } - } - - @Test public void onMotionEvent_downEvent_handleInteractionStart() { final MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index e5fac7ac5e0c..00b0c558b4e3 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -263,6 +263,11 @@ public class DpmMockContext extends MockContext { } @Override + public Context getApplicationContext() { + return this; + } + + @Override public PackageManager getPackageManager() { return mMockSystemServices.packageManager; } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 58f762204c27..ad1537e270e8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -261,7 +261,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // === Test for app side APIs === /** Test for {@link android.content.pm.ShortcutManager#getMaxShortcutCountForActivity()} */ - public void testGetMaxDynamicShortcutCount() { + public void disabled_testGetMaxDynamicShortcutCount() { assertEquals(MAX_SHORTCUTS, mManager.getMaxShortcutCountForActivity()); } @@ -793,7 +793,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(2, mManager.getRemainingCallCount()); } - public void testDeleteAllDynamicShortcuts() { + public void disabled_testDeleteAllDynamicShortcuts() { final ShortcutInfo si1 = makeShortcut("shortcut1"); final ShortcutInfo si2 = makeShortcut("shortcut2"); final ShortcutInfo si3 = makeShortcut("shortcut3"); @@ -1036,7 +1036,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { */ } - public void testCleanupDanglingBitmaps() throws Exception { + public void disabled_testCleanupDanglingBitmaps() throws Exception { assertBitmapDirectories(USER_10, EMPTY_STRINGS); assertBitmapDirectories(USER_11, EMPTY_STRINGS); @@ -1702,7 +1702,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { "s2"); } - public void testCachedShortcuts_accessShortcutsPermission() { + public void disabled_testCachedShortcuts_accessShortcutsPermission() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"), @@ -1744,7 +1744,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s3"); } - public void testCachedShortcuts_canPassShortcutLimit() { + public void disabled_testCachedShortcuts_canPassShortcutLimit() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4"); @@ -2362,7 +2362,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * This is similar to the above test, except it used "disable" instead of "remove". It also * does "enable". */ - public void testDisableAndEnableShortcuts() { + public void disabled_testDisableAndEnableShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000); final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000); @@ -2487,7 +2487,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void testDisableShortcuts_thenRepublish() { + public void disabled_testDisableShortcuts_thenRepublish() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); @@ -4230,7 +4230,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check all other fields } - public void testCleanupPackage() { + public void disabled_testCleanupPackage() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s0_1")))); @@ -4507,7 +4507,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mService.saveDirtyInfo(); } - public void testCleanupPackage_republishManifests() { + public void disabled_testCleanupPackage_republishManifests() { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), R.xml.shortcut_2); @@ -4847,7 +4847,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(expected, spi.canRestoreTo(mService, pi, true)); } - public void testCanRestoreTo() { + public void disabled_testCanRestoreTo() { addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1"); addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 10, "sig1", "sig2"); addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 10, "sig1"); @@ -5785,7 +5785,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_success(/*firstRestore=*/ true); } - public void testBackupAndRestore_restoreToSuperSetSignatures() { + public void disabled_testBackupAndRestore_restoreToSuperSetSignatures() { prepareForBackupTest(); // Change package signatures. diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 5dea44d6ebf4..67e85ff2445d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -4495,6 +4495,27 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testBubblePreference_sameVersionWithSAWPermission() throws Exception { + when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(), + anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED); + + final String xml = "<ranking version=\"4\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n" + + "<channel id=\"someId\" name=\"hi\"" + + " importance=\"3\"/>" + + "</package>" + + "</ranking>"; + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O)); + assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); + } + + @Test public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception { when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(), anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java index d5548a4f375e..f091a65cfe46 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java @@ -497,19 +497,19 @@ public class DeviceAdapterTest { private VibratorController createEmptyVibratorController(int vibratorId) { return new FakeVibratorControllerProvider(mTestLooper.getLooper()) - .newVibratorController(vibratorId, (id, vibrationId) -> {}); + .newVibratorController(vibratorId, (id, vibrationId, stepId) -> {}); } private VibratorController createBasicVibratorController(int vibratorId) { FakeVibratorControllerProvider provider = createVibratorProviderWithEffects( IVibrator.CAP_COMPOSE_EFFECTS); - return provider.newVibratorController(vibratorId, (id, vibrationId) -> {}); + return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {}); } private VibratorController createPwleWithoutFrequenciesVibratorController(int vibratorId) { FakeVibratorControllerProvider provider = createVibratorProviderWithEffects( IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS); - return provider.newVibratorController(vibratorId, (id, vibrationId) -> {}); + return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {}); } private VibratorController createPwleVibratorController(int vibratorId) { @@ -519,7 +519,7 @@ public class DeviceAdapterTest { provider.setMinFrequency(TEST_MIN_FREQUENCY); provider.setFrequencyResolution(TEST_FREQUENCY_RESOLUTION); provider.setMaxAmplitudes(TEST_AMPLITUDE_MAP); - return provider.newVibratorController(vibratorId, (id, vibrationId) -> {}); + return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {}); } private VibratorController createPwleV2VibratorController(int vibratorId) { @@ -538,7 +538,7 @@ public class DeviceAdapterTest { provider.setMinEnvelopeEffectControlPointDurationMillis( TEST_MIN_ENVELOPE_EFFECT_CONTROL_POINT_DURATION_MILLIS); - return provider.newVibratorController(vibratorId, (id, vibrationId) -> {}); + return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {}); } private FakeVibratorControllerProvider createVibratorProviderWithEffects(int... capabilities) { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 5f2af0a085c3..42279e40fa33 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -17,6 +17,9 @@ package com.android.server.vibrator; import static android.os.VibrationAttributes.USAGE_RINGTONE; +import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK; +import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN; +import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK; import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; import static android.os.VibrationEffect.VibrationParameter.targetFrequency; @@ -66,7 +69,6 @@ import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; @@ -153,9 +155,10 @@ public class VibrationThreadTest { when(mPackageManagerInternalMock.getSystemUiServiceComponent()) .thenReturn(new ComponentName("", "")); doAnswer(answer -> { - mVibrationConductor.notifyVibratorComplete(answer.getArgument(0)); + mVibrationConductor.notifyVibratorComplete( + answer.getArgument(0), answer.getArgument(2)); return null; - }).when(mControllerCallbacks).onComplete(anyInt(), anyLong()); + }).when(mControllerCallbacks).onComplete(anyInt(), anyLong(), anyLong()); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock); @@ -190,7 +193,7 @@ public class VibrationThreadTest { HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id)); + verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED); } @@ -203,7 +206,7 @@ public class VibrationThreadTest { HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id)); + verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED); } @@ -217,7 +220,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); @@ -234,7 +237,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); @@ -254,7 +257,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); @@ -265,7 +268,7 @@ public class VibrationThreadTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) + @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() { // No user settings scale. setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, @@ -291,7 +294,7 @@ public class VibrationThreadTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) + @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() { // No user settings scale. setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, @@ -412,6 +415,7 @@ public class VibrationThreadTest { .isEqualTo(expectedOneShots(200L, 50L)); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @LargeTest @Test public void vibrate_singleVibratorRepeatingPatternWithZeroDurationSteps_repeatsEffectCorrectly() @@ -443,6 +447,30 @@ public class VibrationThreadTest { .isEqualTo(expectedOneShots(200L, 150L, 300L, 150L, 300L)); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) + @Test + public void vibrate_singleVibratorPatternWithCallbackDelay_oldCallbacksIgnored() { + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); + fakeVibrator.setCompletionCallbackDelay(100); // 100ms delay to notify service. + fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[]{0, 200, 50, 400}, /* repeat= */ -1); + HalVibration vibration = startThreadAndDispatcher(effect); + waitForCompletion(800 + TEST_TIMEOUT_MILLIS); // 200 + 50 + 400 + 100ms delay + + verifyCallbacksTriggered(vibration, Status.FINISHED); + assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); + + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), eq(1L)); + // Step id = 2 skipped by the 50ms OFF step after the 200ms ON step. + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), eq(3L)); + + // First callback ignored, did not cause the vibrator to turn back on during the 400ms step. + assertThat(fakeVibrator.getEffectSegments(vibration.id)) + .isEqualTo(expectedOneShots(200L, 400L)); + } + @Test public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); @@ -479,12 +507,12 @@ public class VibrationThreadTest { throws Exception { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK); + fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK); fakeVibrator.setCompositionSizeMax(10); VibrationEffect effect = VibrationEffect.startComposition() // Very long delay so thread will be cancelled after first PWLE is triggered. - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(PRIMITIVE_CLICK, 1f, 100) .compose(); VibrationEffect repeatingEffect = VibrationEffect.startComposition() .repeatEffectIndefinitely(effect) @@ -561,13 +589,12 @@ public class VibrationThreadTest { public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately() throws Exception { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK); VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(PRIMITIVE_CLICK, 1f, 100) .compose(); HalVibration vibration = startThreadAndDispatcher(effect); @@ -591,7 +618,7 @@ public class VibrationThreadTest { } @Test - @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrate_singleVibratorVendorEffectCancel_cancelsVibrationImmediately() throws Exception { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS); @@ -657,7 +684,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); @@ -678,7 +705,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); @@ -695,13 +722,14 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L)); verify(mManagerHooks, never()).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks, never()) + .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty()); } @Test - @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrate_singleVibratorVendorEffect_runsVibration() { mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS); @@ -712,7 +740,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); @@ -725,24 +753,23 @@ public class VibrationThreadTest { public void vibrate_singleVibratorComposed_runsVibration() { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK, - VibrationEffect.Composition.PRIMITIVE_TICK); + fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK); VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) + .addPrimitive(PRIMITIVE_CLICK, 1f) + .addPrimitive(PRIMITIVE_TICK, 0.5f) .compose(); HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( - expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0), - expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)), + expectedPrimitive(PRIMITIVE_CLICK, 1, 0), + expectedPrimitive(PRIMITIVE_TICK, 0.5f, 0)), fakeVibrator.getEffectSegments(vibration.id)); } @@ -750,14 +777,15 @@ public class VibrationThreadTest { @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) public void vibrate_singleVibratorComposedAndNoCapability_triggersHalAndReturnsUnsupported() { VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) + .addPrimitive(PRIMITIVE_CLICK, 1f) .compose(); HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L)); verify(mManagerHooks, never()).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks, never()) + .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty()); } @@ -766,14 +794,15 @@ public class VibrationThreadTest { @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() { VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) + .addPrimitive(PRIMITIVE_CLICK, 1f) .compose(); HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong()); verify(mManagerHooks, never()).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks, never()) + .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty()); } @@ -782,34 +811,30 @@ public class VibrationThreadTest { public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - fakeVibrator.setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK, - VibrationEffect.Composition.PRIMITIVE_TICK, - VibrationEffect.Composition.PRIMITIVE_SPIN); + fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_SPIN); fakeVibrator.setCompositionSizeMax(2); VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f) + .addPrimitive(PRIMITIVE_CLICK, 1f) + .addPrimitive(PRIMITIVE_TICK, 0.5f) + .addPrimitive(PRIMITIVE_SPIN, 0.8f) .compose(); HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verifyCallbacksTriggered(vibration, Status.FINISHED); // Vibrator compose called twice. - verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks, times(2)) + .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); assertEquals(3, fakeVibrator.getEffectSegments(vibration.id).size()); } @Test - @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - fakeVibrator.setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK, - VibrationEffect.Composition.PRIMITIVE_TICK); + fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL); fakeVibrator.setMinFrequency(100); @@ -820,8 +845,8 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.startComposition() .addEffect(VibrationEffect.createOneShot(10, 100)) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) + .addPrimitive(PRIMITIVE_CLICK, 1f) + .addPrimitive(PRIMITIVE_TICK, 0.5f) .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .addEffect(VibrationEffect.startWaveform() .addTransition(Duration.ofMillis(10), @@ -836,13 +861,14 @@ public class VibrationThreadTest { // Use first duration the vibrator is turned on since we cannot estimate the clicks. verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks, times(5)) + .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( expectedOneShot(10), - expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0), - expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0), + expectedPrimitive(PRIMITIVE_CLICK, 1, 0), + expectedPrimitive(PRIMITIVE_TICK, 0.5f, 0), expectedPrebaked(VibrationEffect.EFFECT_CLICK), expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f, /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10), @@ -857,17 +883,15 @@ public class VibrationThreadTest { public void vibrate_singleVibratorComposedWithFallback_replacedInTheMiddleOfComposition() { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - fakeVibrator.setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK, - VibrationEffect.Composition.PRIMITIVE_TICK); + fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); VibrationEffect fallback = VibrationEffect.createOneShot(10, 100); VibrationEffect effect = VibrationEffect.startComposition() .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) + .addPrimitive(PRIMITIVE_CLICK, 1f) .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK)) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) + .addPrimitive(PRIMITIVE_TICK, 0.5f) .compose(); HalVibration vibration = createVibration(CombinedVibration.createParallel(effect)); vibration.fillFallbacks(unused -> fallback); @@ -877,7 +901,8 @@ public class VibrationThreadTest { // Use first duration the vibrator is turned on since we cannot estimate the clicks. verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong()); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks, times(4)) + .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); @@ -894,7 +919,7 @@ public class VibrationThreadTest { } @Test - @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void vibrate_singleVibratorPwle_runsComposePwleV2() { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2); @@ -915,7 +940,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( @@ -929,7 +954,7 @@ public class VibrationThreadTest { } @Test - @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void vibrate_singleVibratorBasicPwle_runsComposePwleV2() { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2); @@ -951,7 +976,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(220L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( @@ -964,7 +989,7 @@ public class VibrationThreadTest { } @Test - @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void vibrate_singleVibratorPwle_withInitialFrequency_runsComposePwleV2() { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2); @@ -987,7 +1012,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( @@ -1001,7 +1026,7 @@ public class VibrationThreadTest { } @Test - @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void vibrate_singleVibratorPwle_TooManyControlPoints_splitsAndRunsComposePwleV2() { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2); @@ -1027,7 +1052,8 @@ public class VibrationThreadTest { verifyCallbacksTriggered(vibration, Status.FINISHED); // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments. // Using best split points instead of max-packing PWLEs. - verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks, times(3)) + .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( @@ -1043,7 +1069,7 @@ public class VibrationThreadTest { } @Test - @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void vibrate_singleVibratorPwle_runsComposePwle() { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); @@ -1066,7 +1092,7 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( @@ -1109,7 +1135,8 @@ public class VibrationThreadTest { // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments. // Using best split points instead of max-packing PWLEs. - verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks, times(3)) + .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); assertEquals(6, fakeVibrator.getEffectSegments(vibration.id).size()); } @@ -1160,8 +1187,8 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); - verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); + verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); @@ -1183,9 +1210,9 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id)); - verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id)); - verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong()); + verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong()); + verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); @@ -1207,11 +1234,10 @@ public class VibrationThreadTest { mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - mVibratorProviders.get(4).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(4).setSupportedPrimitives(PRIMITIVE_CLICK); VibrationEffect composed = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_CLICK) .compose(); CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) @@ -1225,10 +1251,10 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id)); - verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id)); - verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id)); - verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong()); + verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong()); + verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong()); + verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); @@ -1243,8 +1269,7 @@ public class VibrationThreadTest { assertEquals(Arrays.asList(expectedOneShot(20)), mVibratorProviders.get(3).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes()); - assertEquals(Arrays.asList( - expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), + assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)), mVibratorProviders.get(4).getEffectSegments(vibration.id)); } @@ -1253,12 +1278,11 @@ public class VibrationThreadTest { mockVibrators(1, 2, 3); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - mVibratorProviders.get(2).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK); mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK); VibrationEffect composed = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_CLICK) .compose(); CombinedVibration effect = CombinedVibration.startSequential() .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), /* delay= */ 50) @@ -1268,10 +1292,10 @@ public class VibrationThreadTest { HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - InOrder controllerVerifier = inOrder(mControllerCallbacks); - controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id)); - controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id)); - controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id)); + InOrder verifier = inOrder(mControllerCallbacks); + verifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong()); + verifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong()); + verifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong()); InOrder batteryVerifier = inOrder(mManagerHooks); batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); @@ -1289,8 +1313,7 @@ public class VibrationThreadTest { assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes()); - assertEquals(Arrays.asList( - expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), + assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)), mVibratorProviders.get(2).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), mVibratorProviders.get(3).getEffectSegments(vibration.id)); @@ -1301,15 +1324,13 @@ public class VibrationThreadTest { int[] vibratorIds = new int[]{1, 2}; mockVibrators(vibratorIds); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - mVibratorProviders.get(1).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(1).setSupportedPrimitives(PRIMITIVE_CLICK); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - mVibratorProviders.get(2).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK); when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true); VibrationEffect composed = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100) + .addPrimitive(PRIMITIVE_CLICK, 1, 100) .compose(); CombinedVibration effect = CombinedVibration.createParallel(composed); // We create the HalVibration here to obtain the vibration id and use it to mock the @@ -1331,8 +1352,7 @@ public class VibrationThreadTest { verify(mManagerHooks, never()).cancelSyncedVibration(); verifyCallbacksTriggered(vibration, Status.FINISHED); - VibrationEffectSegment expected = expectedPrimitive( - VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100); + VibrationEffectSegment expected = expectedPrimitive(PRIMITIVE_CLICK, 1, 100); assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expected), @@ -1345,12 +1365,11 @@ public class VibrationThreadTest { mockVibrators(vibratorIds); mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK); mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - mVibratorProviders.get(4).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(4).setSupportedPrimitives(PRIMITIVE_CLICK); when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); VibrationEffect composed = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_CLICK) .compose(); CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) @@ -1463,9 +1482,9 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id)); - verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id)); - verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong()); + verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong()); + verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); @@ -1505,7 +1524,7 @@ public class VibrationThreadTest { waitForCompletion(rampDownDuration + TEST_TIMEOUT_MILLIS); long completionTime = SystemClock.elapsedRealtime(); - verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); // Vibration ends after duration, thread completed after ramp down assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration); assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + rampDownDuration); @@ -1534,7 +1553,7 @@ public class VibrationThreadTest { waitForCompletion(TEST_TIMEOUT_MILLIS); - verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackDelay); } @@ -1563,7 +1582,8 @@ public class VibrationThreadTest { waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS); long completionTime = SystemClock.elapsedRealtime(); - verify(mControllerCallbacks, never()).onComplete(VIBRATOR_ID, vibration.id); + verify(mControllerCallbacks, never()) + .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); // Vibration ends and thread completes after timeout, before the HAL callback assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackTimeout); assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + callbackDelay); @@ -1639,15 +1659,14 @@ public class VibrationThreadTest { mockVibrators(1, 2); mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - mVibratorProviders.get(2).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK); CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .addVibrator(2, VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(PRIMITIVE_CLICK, 1f, 100) .compose()) .combine(); HalVibration vibration = startThreadAndDispatcher(effect); @@ -1672,7 +1691,7 @@ public class VibrationThreadTest { } @Test - @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrate_multipleVendorEffectCancel_cancelsVibrationImmediately() throws Exception { mockVibrators(1, 2); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS); @@ -1767,7 +1786,7 @@ public class VibrationThreadTest { HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); // Duration extended for 5 + 5 + 5 + 15. @@ -1847,7 +1866,7 @@ public class VibrationThreadTest { HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), @@ -1856,7 +1875,7 @@ public class VibrationThreadTest { } @Test - @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrate_vendorEffectWithRampDown_doesNotAddRampDown() { when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS); @@ -1865,7 +1884,7 @@ public class VibrationThreadTest { HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibration.id)) @@ -1879,26 +1898,24 @@ public class VibrationThreadTest { when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL, IVibrator.CAP_COMPOSE_EFFECTS); - mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK); VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_CLICK) .compose(); HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); - assertEquals( - Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), + assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)), mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty()); } @Test - @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void vibrate_pwleWithRampDown_doesNotAddRampDown() { when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); @@ -1916,7 +1933,7 @@ public class VibrationThreadTest { HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); verifyCallbacksTriggered(vibration, Status.FINISHED); assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)), @@ -1928,8 +1945,7 @@ public class VibrationThreadTest { public void vibrate_multipleVibrations_withCancel() throws Exception { mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects( VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK); - mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL, IVibrator.CAP_COMPOSE_EFFECTS); @@ -1940,7 +1956,7 @@ public class VibrationThreadTest { .repeatEffectIndefinitely(VibrationEffect.get(VibrationEffect.EFFECT_TICK)) .compose(); VibrationEffect effect3 = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_CLICK) .compose(); VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100); VibrationEffect effect5 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); @@ -1974,14 +1990,15 @@ public class VibrationThreadTest { assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); // Effect1 - verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration1.id); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration1.id), anyLong()); verifyCallbacksTriggered(vibration1, Status.FINISHED); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), fakeVibrator.getEffectSegments(vibration1.id)); // Effect2: repeating, cancelled. - verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibration2.id); + verify(mControllerCallbacks, atLeast(2)) + .onComplete(eq(VIBRATOR_ID), eq(vibration2.id), anyLong()); verifyCallbacksTriggered(vibration2, Status.CANCELLED_BY_USER); // The exact count of segments might vary, so just check that there's more than 2 and @@ -1994,10 +2011,9 @@ public class VibrationThreadTest { } // Effect3 - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id)); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id), anyLong()); verifyCallbacksTriggered(vibration3, Status.FINISHED); - assertEquals(Arrays.asList( - expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), + assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)), fakeVibrator.getEffectSegments(vibration3.id)); // Effect4: cancelled quickly. @@ -2014,8 +2030,7 @@ public class VibrationThreadTest { mockVibrators(1, 2, 3); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - mVibratorProviders.get(2).setSupportedPrimitives( - VibrationEffect.Composition.PRIMITIVE_CLICK); + mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK); mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK); CombinedVibration effect = CombinedVibration.startSequential() @@ -2029,8 +2044,7 @@ public class VibrationThreadTest { /* delay= */ TEST_TIMEOUT_MILLIS) .addNext(2, VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, - /* delay= */ TEST_TIMEOUT_MILLIS) + .addPrimitive(PRIMITIVE_CLICK, 1, /* delay= */ TEST_TIMEOUT_MILLIS) .compose(), /* delay= */ TEST_TIMEOUT_MILLIS) .combine(); @@ -2051,8 +2065,7 @@ public class VibrationThreadTest { assertEquals(Arrays.asList(expectedOneShot(TEST_TIMEOUT_MILLIS)), mVibratorProviders.get(1).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(255), mVibratorProviders.get(1).getAmplitudes()); - assertEquals(Arrays.asList(expectedPrimitive( - VibrationEffect.Composition.PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS)), + assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS)), mVibratorProviders.get(2).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), mVibratorProviders.get(3).getEffectSegments(vibration.id)); @@ -2205,7 +2218,7 @@ public class VibrationThreadTest { private void verifyCallbacksTriggered(HalVibration vibration, Status expectedStatus) { assertThat(vibration.getStatus()).isEqualTo(expectedStatus); - verify(mManagerHooks).onVibrationThreadReleased(vibration.id); + verify(mManagerHooks).onVibrationThreadReleased(eq(vibration.id)); } private static final class TestLooperAutoDispatcher extends Thread { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java index 0978f48491cc..4df13deaed7d 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java @@ -203,9 +203,10 @@ public class VibratorControllerTest { @Test public void setAmplitude_vibratorVibrating_setsAmplitude() { - when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0)); + when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong())) + .thenAnswer(args -> args.getArgument(0)); VibratorController controller = createController(); - controller.on(100, /* vibrationId= */ 1); + controller.on(100, 1, 1); assertTrue(controller.isVibrating()); assertEquals(-1, controller.getCurrentAmplitude(), /* delta= */ 0); @@ -215,81 +216,84 @@ public class VibratorControllerTest { @Test public void on_withDuration_turnsVibratorOn() { - when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0)); + when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong())) + .thenAnswer(args -> args.getArgument(0)); VibratorController controller = createController(); - controller.on(100, 10); + controller.on(100, 10, 20); assertTrue(controller.isVibrating()); - verify(mNativeWrapperMock).on(eq(100L), eq(10L)); + verify(mNativeWrapperMock).on(eq(100L), eq(10L), eq(20L)); } @Test public void on_withPrebaked_performsEffect() { - when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L); + when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong(), anyLong())) + .thenReturn(10L); VibratorController controller = createController(); PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_STRENGTH_MEDIUM); - assertEquals(10L, controller.on(prebaked, 11)); + assertEquals(10L, controller.on(prebaked, 11, 23)); assertTrue(controller.isVibrating()); verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK), - eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L)); + eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L), eq(23L)); } @Test public void on_withComposed_performsEffect() { mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L); + when(mNativeWrapperMock.compose(any(), anyLong(), anyLong())).thenReturn(15L); VibratorController controller = createController(); PrimitiveSegment[] primitives = new PrimitiveSegment[]{ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) }; - assertEquals(15L, controller.on(primitives, 12)); + assertEquals(15L, controller.on(primitives, 12, 34)); assertTrue(controller.isVibrating()); - verify(mNativeWrapperMock).compose(eq(primitives), eq(12L)); + verify(mNativeWrapperMock).compose(eq(primitives), eq(12L), eq(34L)); } @Test public void on_withComposedPwle_performsEffect() { mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); - when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong())).thenReturn(15L); + when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong(), anyLong())).thenReturn(15L); VibratorController controller = createController(); RampSegment[] primitives = new RampSegment[]{ new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1, /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 10) }; - assertEquals(15L, controller.on(primitives, 12)); + assertEquals(15L, controller.on(primitives, 12, 45)); assertTrue(controller.isVibrating()); - verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L)); + verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L), eq(45L)); } @Test public void on_withComposedPwleV2_performsEffect() { mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2); - when(mNativeWrapperMock.composePwleV2(any(), anyLong())).thenReturn(15L); + when(mNativeWrapperMock.composePwleV2(any(), anyLong(), anyLong())).thenReturn(15L); VibratorController controller = createController(); PwlePoint[] primitives = new PwlePoint[]{ new PwlePoint(/*amplitude=*/ 0, /*frequencyHz=*/ 100, /*timeMillis=*/ 0), new PwlePoint(/*amplitude=*/ 1, /*frequencyHz=*/ 200, /*timeMillis=*/ 10) }; - assertEquals(15L, controller.on(primitives, 12)); + assertEquals(15L, controller.on(primitives, 12, 53)); assertTrue(controller.isVibrating()); - verify(mNativeWrapperMock).composePwleV2(eq(primitives), eq(12L)); + verify(mNativeWrapperMock).composePwleV2(eq(primitives), eq(12L), eq(53L)); } @Test public void off_turnsOffVibrator() { - when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0)); + when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong())) + .thenAnswer(args -> args.getArgument(0)); VibratorController controller = createController(); - controller.on(100, 1); + controller.on(100, 1, 1); assertTrue(controller.isVibrating()); controller.off(); @@ -301,10 +305,11 @@ public class VibratorControllerTest { @Test public void reset_turnsOffVibratorAndDisablesExternalControl() { mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); - when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0)); + when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong())) + .thenAnswer(args -> args.getArgument(0)); VibratorController controller = createController(); - controller.on(100, 1); + controller.on(100, 1, 1); assertTrue(controller.isVibrating()); controller.reset(); @@ -315,12 +320,13 @@ public class VibratorControllerTest { @Test public void registerVibratorStateListener_callbacksAreTriggered() throws Exception { - when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0)); + when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong())) + .thenAnswer(args -> args.getArgument(0)); VibratorController controller = createController(); controller.registerVibratorStateListener(mVibratorStateListenerMock); - controller.on(10, 1); - controller.on(100, 2); + controller.on(10, 1, 1); + controller.on(100, 2, 1); controller.off(); controller.off(); @@ -334,19 +340,20 @@ public class VibratorControllerTest { @Test public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception { - when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0)); + when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong())) + .thenAnswer(args -> args.getArgument(0)); VibratorController controller = createController(); controller.registerVibratorStateListener(mVibratorStateListenerMock); verify(mVibratorStateListenerMock).onVibrating(false); - controller.on(10, 1); + controller.on(10, 1, 1); verify(mVibratorStateListenerMock).onVibrating(true); controller.unregisterVibratorStateListener(mVibratorStateListenerMock); Mockito.clearInvocations(mVibratorStateListenerMock); - controller.on(10, 1); + controller.on(10, 1, 1); verifyNoMoreInteractions(mVibratorStateListenerMock); } diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java index 3f3476716831..bd806b7f7f37 100644 --- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -118,11 +118,11 @@ public final class FakeVibratorControllerProvider { } @Override - public long on(long milliseconds, long vibrationId) { + public long on(long milliseconds, long vibrationId, long stepId) { recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, /* frequencyHz= */ 0, (int) milliseconds)); applyLatency(mOnLatency); - scheduleListener(milliseconds, vibrationId); + scheduleListener(milliseconds, vibrationId, stepId); return milliseconds; } @@ -139,7 +139,7 @@ public final class FakeVibratorControllerProvider { } @Override - public long perform(long effect, long strength, long vibrationId) { + public long perform(long effect, long strength, long vibrationId, long stepId) { if (mSupportedEffects == null || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) { return 0; @@ -147,13 +147,13 @@ public final class FakeVibratorControllerProvider { recordEffectSegment(vibrationId, new PrebakedSegment((int) effect, false, (int) strength)); applyLatency(mOnLatency); - scheduleListener(EFFECT_DURATION, vibrationId); + scheduleListener(EFFECT_DURATION, vibrationId, stepId); return EFFECT_DURATION; } @Override public long performVendorEffect(Parcel vendorData, long strength, float scale, - float adaptiveScale, long vibrationId) { + float adaptiveScale, long vibrationId, long stepId) { if ((mCapabilities & IVibrator.CAP_PERFORM_VENDOR_EFFECTS) == 0) { return 0; } @@ -161,13 +161,13 @@ public final class FakeVibratorControllerProvider { recordVendorEffect(vibrationId, new VibrationEffect.VendorEffect(bundle, (int) strength, scale, adaptiveScale)); applyLatency(mOnLatency); - scheduleListener(mVendorEffectDuration, vibrationId); + scheduleListener(mVendorEffectDuration, vibrationId, stepId); // HAL has unknown duration for vendor effects. return Long.MAX_VALUE; } @Override - public long compose(PrimitiveSegment[] primitives, long vibrationId) { + public long compose(PrimitiveSegment[] primitives, long vibrationId, long stepId) { if (mSupportedPrimitives == null) { return 0; } @@ -182,12 +182,13 @@ public final class FakeVibratorControllerProvider { recordEffectSegment(vibrationId, primitive); } applyLatency(mOnLatency); - scheduleListener(duration, vibrationId); + scheduleListener(duration, vibrationId, stepId); return duration; } @Override - public long composePwle(RampSegment[] primitives, int braking, long vibrationId) { + public long composePwle(RampSegment[] primitives, int braking, long vibrationId, + long stepId) { long duration = 0; for (RampSegment primitive : primitives) { duration += primitive.getDuration(); @@ -195,19 +196,19 @@ public final class FakeVibratorControllerProvider { } recordBraking(vibrationId, braking); applyLatency(mOnLatency); - scheduleListener(duration, vibrationId); + scheduleListener(duration, vibrationId, stepId); return duration; } @Override - public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) { + public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId, long stepId) { long duration = 0; for (PwlePoint pwlePoint: pwlePoints) { duration += pwlePoint.getTimeMillis(); recordEffectPwlePoint(vibrationId, pwlePoint); } applyLatency(mOnLatency); - scheduleListener(duration, vibrationId); + scheduleListener(duration, vibrationId, stepId); return duration; } @@ -263,8 +264,8 @@ public final class FakeVibratorControllerProvider { } } - private void scheduleListener(long vibrationDuration, long vibrationId) { - mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId), + private void scheduleListener(long vibrationDuration, long vibrationId, long stepId) { + mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId, stepId), vibrationDuration + mCompletionCallbackDelay); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index d53ba1d24d8c..301a7544227f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -56,8 +56,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_PIP; import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN; @@ -921,12 +919,6 @@ public class ActivityRecordTests extends WindowTestsBase { // animation and AR#takeSceneTransitionInfo also clear the AR#mPendingOptions assertNull(activity.takeSceneTransitionInfo()); assertNull(activity.getOptions()); - - final AppTransition appTransition = activity.mDisplayContent.mAppTransition; - spyOn(appTransition); - activity.applyOptionsAnimation(); - - verify(appTransition).overridePendingAppTransitionRemote(any()); } @Test @@ -1190,7 +1182,6 @@ public class ActivityRecordTests extends WindowTestsBase { FINISH_RESULT_REQUESTED, activity.finishIfPossible("test", false /* oomAdj */)); assertEquals(PAUSING, activity.getState()); verify(activity).setVisibility(eq(false)); - verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE)); } /** @@ -1237,7 +1228,6 @@ public class ActivityRecordTests extends WindowTestsBase { activity.finishIfPossible("test", false /* oomAdj */); verify(activity).setVisibility(eq(false)); - verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE)); verify(activity.mDisplayContent, never()).executeAppTransition(); } @@ -1254,7 +1244,6 @@ public class ActivityRecordTests extends WindowTestsBase { activity.finishIfPossible("test", false /* oomAdj */); verify(activity, atLeast(1)).setVisibility(eq(false)); - verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE)); verify(activity.mDisplayContent).executeAppTransition(); } @@ -1275,7 +1264,6 @@ public class ActivityRecordTests extends WindowTestsBase { activity.finishIfPossible("test", false /* oomAdj */); - verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE)); assertFalse(activity.inTransition()); // finishIfPossible -> completeFinishing -> addToFinishingAndWaitForIdle @@ -2657,10 +2645,6 @@ public class ActivityRecordTests extends WindowTestsBase { @Presubmit public void testGetOrientation() { mDisplayContent.setIgnoreOrientationRequest(false); - // ActivityBuilder will resume top activities and cause the activity been added into - // opening apps list. Since this test is focus on the effect of visible on getting - // orientation, we skip app transition to avoid interference. - doNothing().when(mDisplayContent).prepareAppTransition(anyInt()); final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); activity.setVisible(true); @@ -2925,7 +2909,6 @@ public class ActivityRecordTests extends WindowTestsBase { sources.add(activity2); doReturn(true).when(activity2).okToAnimate(); doReturn(true).when(activity2).isAnimating(); - assertTrue(activity2.applyAnimation(null, TRANSIT_OLD_ACTIVITY_OPEN, true, false, sources)); } @Test public void testTrackingStartingWindowThroughTrampoline() { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index d5b9751b0f51..a99bc4966c03 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -37,7 +37,6 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; -import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; @@ -56,6 +55,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; @@ -63,9 +63,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; -import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE; import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; @@ -1742,8 +1739,6 @@ public class DisplayContentTests extends WindowTestsBase { public void testFixedRotationWithPip() { final DisplayContent displayContent = mDefaultDisplay; displayContent.setIgnoreOrientationRequest(false); - // Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded. - doNothing().when(displayContent).prepareAppTransition(anyInt()); // Make resume-top really update the activity state. setBooted(mAtm); clearInvocations(mWm); @@ -1826,7 +1821,7 @@ public class DisplayContentTests extends WindowTestsBase { .setTask(nonTopVisible.getTask()).setVisible(false) .setActivityTheme(android.R.style.Theme_Translucent).build(); final TestTransitionPlayer player = registerTestTransitionPlayer(); - mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0); + mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0, null); translucentTop.setVisibility(true); mDisplayContent.updateOrientation(); assertEquals("Non-top visible activity must be portrait", @@ -2373,33 +2368,6 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test - public void testShowImeScreenshot() { - final Task rootTask = createTask(mDisplayContent); - final Task task = createTaskInRootTask(rootTask, 0 /* userId */); - final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( - activity).build(); - task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); - doReturn(true).when(task).okToAnimate(); - ArrayList<WindowContainer> sources = new ArrayList<>(); - sources.add(activity); - - mDisplayContent.setImeLayeringTarget(win); - spyOn(mDisplayContent); - - // Expecting the IME screenshot only be attached when performing task closing transition. - task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */, - false /* isVoiceInteraction */, sources); - verify(mDisplayContent).showImeScreenshot(); - - clearInvocations(mDisplayContent); - activity.applyAnimation(null, TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE, false /* enter */, - false /* isVoiceInteraction */, sources); - verify(mDisplayContent, never()).showImeScreenshot(); - } - - @SetupWindows(addWindows = W_INPUT_METHOD) - @Test public void testShowImeScreenshot_removeCurSnapshotBeforeCreateNext() { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); @@ -2427,32 +2395,6 @@ public class DisplayContentTests extends WindowTestsBase { @UseTestDisplay(addWindows = {W_INPUT_METHOD}) @Test - public void testRemoveImeScreenshot_whenTargetSurfaceWasInvisible() { - final Task rootTask = createTask(mDisplayContent); - final Task task = createTaskInRootTask(rootTask, 0 /* userId */); - final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( - activity).build(); - win.onSurfaceShownChanged(true); - makeWindowVisible(win, mDisplayContent.mInputMethodWindow); - task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); - doReturn(true).when(task).okToAnimate(); - ArrayList<WindowContainer> sources = new ArrayList<>(); - sources.add(activity); - - mDisplayContent.setImeLayeringTarget(win); - mDisplayContent.setImeInputTarget(win); - mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true); - task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */, - false /* isVoiceInteraction */, sources); - assertNotNull(mDisplayContent.mImeScreenshot); - - win.onSurfaceShownChanged(false); - assertNull(mDisplayContent.mImeScreenshot); - } - - @UseTestDisplay(addWindows = {W_INPUT_METHOD}) - @Test public void testRemoveImeScreenshot_whenWindowRemoveImmediately() { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); @@ -2751,55 +2693,77 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test - public void testImeChildWindowFocusWhenImeLayeringTargetChanges() { - final WindowState imeChildWindow = newWindowBuilder("imeChildWindow", + public void testImeChildWindowFocusWhenImeParentWindowChanges() { + final var imeChildWindow = newWindowBuilder("imeChildWindow", TYPE_APPLICATION_ATTACHED_DIALOG).setParent(mImeWindow).build(); - makeWindowVisibleAndDrawn(imeChildWindow, mImeWindow); - assertTrue(imeChildWindow.canReceiveKeys()); - mDisplayContent.setInputMethodWindowLocked(mImeWindow); - - // Verify imeChildWindow can be focused window if the next IME target requests IME visible. - final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", - TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); - mDisplayContent.setImeLayeringTarget(imeAppTarget); - spyOn(imeAppTarget); - doReturn(true).when(imeAppTarget).isRequestedVisible(ime()); - assertEquals(imeChildWindow, mDisplayContent.findFocusedWindow()); - - // Verify imeChildWindow doesn't be focused window if the next IME target does not - // request IME visible. - final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", - TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); - mDisplayContent.setImeLayeringTarget(nextImeAppTarget); - assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow()); + doTestImeWindowFocusWhenImeParentWindowChanged(imeChildWindow); } @SetupWindows(addWindows = W_INPUT_METHOD) @Test - public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() { - final WindowState imeMenuDialog = newWindowBuilder("imeMenuDialog", + public void testImeDialogWindowFocusWhenImeParentWindowChanges() { + final var imeDialogWindow = newWindowBuilder("imeMenuDialog", TYPE_INPUT_METHOD_DIALOG).build(); - makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow); - assertTrue(imeMenuDialog.canReceiveKeys()); + doTestImeWindowFocusWhenImeParentWindowChanged(imeDialogWindow); + } + + @SetupWindows(addWindows = W_INPUT_METHOD) + @Test + public void testImeWindowFocusWhenImeParentWindowChanges() { + // Verify focusable, non-child IME windows. + final var otherImeWindow = newWindowBuilder("otherImeWindow", + TYPE_INPUT_METHOD).build(); + doTestImeWindowFocusWhenImeParentWindowChanged(otherImeWindow); + } + + private void doTestImeWindowFocusWhenImeParentWindowChanged(@NonNull WindowState window) { + makeWindowVisibleAndDrawn(window, mImeWindow); + assertTrue("Window canReceiveKeys", window.canReceiveKeys()); mDisplayContent.setInputMethodWindowLocked(mImeWindow); - // Verify imeMenuDialog can be focused window if the next IME target requests IME visible. + // Verify window can be focused if the IME parent is visible and the IME is visible. final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); - imeAppTarget.setRequestedVisibleTypes(ime()); - assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); - - // Verify imeMenuDialog doesn't be focused window if the next IME target is closing. + mDisplayContent.updateImeInputAndControlTarget(imeAppTarget); + final var imeProvider = mDisplayContent.getInsetsStateController().getImeSourceProvider(); + imeProvider.setImeShowing(true); + final var imeParentWindow = mDisplayContent.getImeParentWindow(); + assertNotNull("IME parent window is not null", imeParentWindow); + assertTrue("IME parent window is visible", imeParentWindow.isVisibleRequested()); + assertTrue("IME is visible", imeProvider.isImeShowing()); + assertEquals("Window is the focused one", window, mDisplayContent.findFocusedWindow()); + + // Verify window can't be focused if the IME parent is not visible. final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); makeWindowVisibleAndDrawn(nextImeAppTarget); - // Even if the app still requests IME, the ime dialog should not gain focus if the target - // app is invisible. - nextImeAppTarget.setRequestedVisibleTypes(ime()); - nextImeAppTarget.mActivityRecord.setVisibility(false); + // Change layering target but keep input target (and thus imeParent) the same. mDisplayContent.setImeLayeringTarget(nextImeAppTarget); - assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); + // IME parent window is not visible, occluded by new layering target. + imeParentWindow.setVisibleRequested(false); + assertEquals("IME parent window did not change", imeParentWindow, + mDisplayContent.getImeParentWindow()); + assertFalse("IME parent window is not visible", imeParentWindow.isVisibleRequested()); + assertTrue("IME is visible", imeProvider.isImeShowing()); + assertNotEquals("Window is not the focused one when imeParent is not visible", window, + mDisplayContent.findFocusedWindow()); + + // Verify window can be focused if the IME is not visible. + mDisplayContent.updateImeInputAndControlTarget(nextImeAppTarget); + imeProvider.setImeShowing(false); + final var nextImeParentWindow = mDisplayContent.getImeParentWindow(); + assertNotNull("Next IME parent window is not null", nextImeParentWindow); + assertNotEquals("IME parent window changed", imeParentWindow, nextImeParentWindow); + assertTrue("Next IME parent window is visible", nextImeParentWindow.isVisibleRequested()); + assertFalse("IME is not visible", imeProvider.isImeShowing()); + if (window.isChildWindow()) { + assertNotEquals("Child window is not the focused on when the IME is not visible", + window, mDisplayContent.findFocusedWindow()); + } else { + assertEquals("Window is the focused one when the IME is not visible", + window, mDisplayContent.findFocusedWindow()); + } } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java index 7c8a8835c3b8..2d4101e40615 100644 --- a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java @@ -31,7 +31,7 @@ import static org.mockito.ArgumentMatchers.eq; import android.annotation.NonNull; import android.graphics.Rect; 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.view.DisplayInfo; @@ -68,32 +68,14 @@ public class PresentationControllerTests extends WindowTestsBase { @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test public void testPresentationShowAndHide() { - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.copyFrom(mDisplayInfo); - displayInfo.flags = FLAG_PRESENTATION; - final DisplayContent dc = createNewDisplay(displayInfo); - final int displayId = dc.getDisplayId(); - doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId); + final DisplayContent dc = createPresentationDisplay(); final ActivityRecord activity = createActivityRecord(createTask(dc)); assertTrue(activity.isVisible()); - doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled()); - final int uid = 100000; // uid for non-system user - final Session session = createTestSession(mAtm, 1234 /* pid */, uid); - final int userId = UserHandle.getUserId(uid); - doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId)); - final WindowManager.LayoutParams params = new WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_PRESENTATION); - final IWindow clientWindow = new TestIWindow(); - // Show a Presentation window, which requests the activity to be stopped. - final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId, - userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(), - new InsetsSourceControl.Array(), new Rect(), new float[1]); - assertTrue(result >= WindowManagerGlobal.ADD_OKAY); + // Add a presentation window, which requests the activity to stop. + final WindowState window = addPresentationWindow(100000, dc.mDisplayId); assertFalse(activity.isVisibleRequested()); assertTrue(activity.isVisible()); - final WindowState window = mWm.windowForClientLocked(session, clientWindow, false); - window.mHasSurface = true; final Transition addTransition = window.mTransitionController.getCollectingTransition(); assertEquals(TRANSIT_OPEN, addTransition.mType); assertTrue(addTransition.isInTransition(window)); @@ -117,6 +99,51 @@ public class PresentationControllerTests extends WindowTestsBase { assertTrue(activity.isVisible()); } + @DisableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) + @Test + public void testPresentationShowAndHide_flagDisabled() { + final DisplayContent dc = createPresentationDisplay(); + final ActivityRecord activity = createActivityRecord(createTask(dc)); + assertTrue(activity.isVisible()); + + final WindowState window = addPresentationWindow(100000, dc.mDisplayId); + assertFalse(window.mTransitionController.isCollecting()); + assertTrue(activity.isVisibleRequested()); + assertTrue(activity.isVisible()); + + window.removeIfPossible(); + assertFalse(window.mTransitionController.isCollecting()); + assertTrue(activity.isVisibleRequested()); + assertTrue(activity.isVisible()); + assertFalse(window.isAttached()); + } + + private WindowState addPresentationWindow(int uid, int displayId) { + final Session session = createTestSession(mAtm, 1234 /* pid */, uid); + final int userId = UserHandle.getUserId(uid); + doReturn(true).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId)); + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_PRESENTATION); + final IWindow clientWindow = new TestIWindow(); + final int res = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId, + userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(), + new InsetsSourceControl.Array(), new Rect(), new float[1]); + assertTrue(res >= WindowManagerGlobal.ADD_OKAY); + final WindowState window = mWm.windowForClientLocked(session, clientWindow, false); + window.mHasSurface = true; + return window; + } + + private DisplayContent createPresentationDisplay() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.copyFrom(mDisplayInfo); + displayInfo.flags = FLAG_PRESENTATION; + final DisplayContent dc = createNewDisplay(displayInfo); + final int displayId = dc.getDisplayId(); + doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId); + return dc; + } + private void completeTransition(@NonNull Transition transition, boolean abortSync) { final ActionChain chain = ActionChain.testFinish(transition); if (abortSync) { diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 9406779c929d..3776b03695d5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -264,7 +264,7 @@ public class SystemServicesTestRule implements TestRule { final DisplayManagerGlobal dmg = DisplayManagerGlobal.getInstance(); spyOn(dmg); doNothing().when(dmg).registerDisplayListener( - any(), any(Executor.class), anyLong(), anyString()); + any(), any(Executor.class), anyLong(), anyString(), anyBoolean()); doNothing().when(dmg).registerTopologyListener(any(Executor.class), any(), anyString()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 2ee34d3a4b36..c0cb09f9a7d7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -82,8 +82,6 @@ import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; @@ -104,7 +102,6 @@ import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.internal.graphics.ColorUtils; -import com.android.window.flags.Flags; import org.junit.Test; import org.junit.runner.RunWith; @@ -683,7 +680,7 @@ public class TransitionTests extends WindowTestsBase { app.onStartingWindowDrawn(); // The task appeared event should be deferred until transition ready. assertFalse(task.taskAppearedReady()); - testPlayer.onTransactionReady(app.getSyncTransaction()); + testPlayer.onTransactionReady(); assertTrue(task.taskAppearedReady()); assertTrue(playerProc.isRunningRemoteTransition()); assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread())); @@ -1162,7 +1159,8 @@ public class TransitionTests extends WindowTestsBase { screenDecor.updateSurfacePosition(mMockT); assertEquals(prevPos, screenDecor.mLastSurfacePosition); - final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction startTransaction = mTransaction; + clearInvocations(startTransaction); final SurfaceControl.TransactionCommittedListener transactionCommittedListener = onRotationTransactionReady(player, startTransaction); @@ -1213,7 +1211,8 @@ public class TransitionTests extends WindowTestsBase { assertFalse(statusBar.mToken.inTransition()); assertTrue(app.getTask().inTransition()); - final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction startTransaction = mTransaction; + clearInvocations(startTransaction); final SurfaceControl leash = statusBar.mToken.getAnimationLeash(); doReturn(true).when(leash).isValid(); final SurfaceControl.TransactionCommittedListener transactionCommittedListener = @@ -1287,7 +1286,8 @@ public class TransitionTests extends WindowTestsBase { // Avoid DeviceStateController disturbing the test by triggering another rotation change. doReturn(false).when(mDisplayContent).updateRotationUnchecked(); - onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted(); + clearInvocations(mTransaction); + onRotationTransactionReady(player, mTransaction).onTransactionCommitted(); assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange( mDisplayContent.mRemoteToken.toWindowContainerToken()).getRotationAnimation()); spyOn(navBarInsetsProvider); @@ -1350,7 +1350,7 @@ public class TransitionTests extends WindowTestsBase { mDisplayContent.setFixedRotationLaunchingAppUnchecked(home); doReturn(true).when(home).hasFixedRotationTransform(any()); player.startTransition(); - player.onTransactionReady(mDisplayContent.getSyncTransaction()); + player.onTransactionReady(); final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation(); final RemoteDisplayChangeController displayChangeController = mDisplayContent @@ -2009,21 +2009,6 @@ public class TransitionTests extends WindowTestsBase { assertEquals(expectedBackgroundColor, info.getChanges().get(1).getBackgroundColor()); } - @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) - @Test - public void testOverrideAnimationOptionsToInfoIfNecessary_disableAnimOptionsPerChange() { - ActivityRecord r = initializeOverrideAnimationOptionsTest(); - TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions - .makeCommonAnimOptions("testPackage"); - mTransition.setOverrideAnimation(options, r, null /* startCallback */, - null /* finishCallback */); - - mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo); - - assertEquals(options, mInfo.getAnimationOptions()); - } - - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testOverrideAnimationOptionsToInfoIfNecessary_fromStyleAnimOptions() { ActivityRecord r = initializeOverrideAnimationOptionsTest(); @@ -2049,7 +2034,6 @@ public class TransitionTests extends WindowTestsBase { options, activityChange.getAnimationOptions()); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testOverrideAnimationOptionsToInfoIfNecessary_sceneAnimOptions() { ActivityRecord r = initializeOverrideAnimationOptionsTest(); @@ -2075,7 +2059,6 @@ public class TransitionTests extends WindowTestsBase { options, activityChange.getAnimationOptions()); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testOverrideAnimationOptionsToInfoIfNecessary_crossProfileAnimOptions() { ActivityRecord r = initializeOverrideAnimationOptionsTest(); @@ -2103,7 +2086,6 @@ public class TransitionTests extends WindowTestsBase { assertTrue(activityChange.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL)); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptions() { ActivityRecord r = initializeOverrideAnimationOptionsTest(); @@ -2136,7 +2118,6 @@ public class TransitionTests extends WindowTestsBase { options.getBackgroundColor(), activityChange.getBackgroundColor()); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testOverrideAnimationOptionsToInfoIfNecessary_haveTaskFragmentAnimParams() { ActivityRecord r = initializeOverrideAnimationOptionsTest(); @@ -2185,7 +2166,6 @@ public class TransitionTests extends WindowTestsBase { options.getBackgroundColor(), activityChange.getBackgroundColor()); } - @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE) @Test public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptionsWithTaskOverride() { ActivityRecord r = initializeOverrideAnimationOptionsTest(); @@ -2415,7 +2395,6 @@ public class TransitionTests extends WindowTestsBase { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) public void testMoveDisplayToTop() { // Set up two displays, each of which has a task. DisplayContent otherDisplay = createNewDisplay(); @@ -3071,8 +3050,11 @@ public class TransitionTests extends WindowTestsBase { TestTransitionPlayer player, SurfaceControl.Transaction startTransaction) { final ArgumentCaptor<SurfaceControl.TransactionCommittedListener> listenerCaptor = ArgumentCaptor.forClass(SurfaceControl.TransactionCommittedListener.class); - player.onTransactionReady(startTransaction); - verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture()); + player.onTransactionReady(); + // The startTransaction is from mWm.mTransactionFactory.get() in SyncGroup#finishNow. + // 2 times are from SyncGroup#finishNow and AsyncRotationController#setupStartTransaction. + verify(startTransaction, times(2)).addTransactionCommittedListener( + any(), listenerCaptor.capture()); return listenerCaptor.getValue(); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index cee98fb1b34c..4f310de1e48b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -30,8 +30,6 @@ import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsets.Type.systemOverlays; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; @@ -1121,7 +1119,6 @@ public class WindowContainerTests extends WindowTestsBase { final ActivityRecord activity = createActivityRecord(mDisplayContent, task); final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( activity).build(); - task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); spyOn(win); doReturn(true).when(task).okToAnimate(); ArrayList<WindowContainer> sources = new ArrayList<>(); @@ -1130,8 +1127,6 @@ public class WindowContainerTests extends WindowTestsBase { // Simulate the task applying the exit transition, verify the main window of the task // will be set the frozen insets state before the animation starts activity.setVisibility(false); - task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */, - false /* isVoiceInteraction */, sources); verify(win).freezeInsetsState(); // Simulate the task transition finished. diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 97c6ac6854c3..c6416850c464 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -2159,13 +2159,14 @@ public class WindowTestsBase extends SystemServiceTestsBase { mOrganizer.startTransition(mLastTransit.getToken(), null); } - void onTransactionReady(SurfaceControl.Transaction t) { - mLastTransit.onTransactionReady(mLastTransit.getSyncId(), t); + void onTransactionReady() { + // SyncGroup#finishNow -> Transition#onTransactionReady. + mController.mSyncEngine.abort(mLastTransit.getSyncId()); } void start() { startTransition(); - onTransactionReady(mock(SurfaceControl.Transaction.class)); + onTransactionReady(); } public void finish() { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java index 49ad46131b0d..df43ed973fe7 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java @@ -24,7 +24,6 @@ import android.telephony.TelephonyManager; import android.util.Slog; import com.android.internal.annotations.GuardedBy; -import com.android.internal.telephony.flags.Flags; import java.util.ArrayList; import java.util.List; @@ -119,28 +118,18 @@ public class PhoneCallStateHandler { private boolean checkCallStatus() { List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList(); if (infoList == null) return false; - if (!Flags.enforceTelephonyFeatureMapping()) { - return infoList.stream() - .filter(s -> (s.getSubscriptionId() - != SubscriptionManager.INVALID_SUBSCRIPTION_ID)) - .anyMatch(s -> isCallOngoingFromState( - mTelephonyManager - .createForSubscriptionId(s.getSubscriptionId()) - .getCallStateForSubscription())); - } else { - return infoList.stream() - .filter(s -> (s.getSubscriptionId() - != SubscriptionManager.INVALID_SUBSCRIPTION_ID)) - .anyMatch(s -> { - try { - return isCallOngoingFromState(mTelephonyManager - .createForSubscriptionId(s.getSubscriptionId()) - .getCallStateForSubscription()); - } catch (UnsupportedOperationException e) { - return false; - } - }); - } + return infoList.stream() + .filter(s -> (s.getSubscriptionId() + != SubscriptionManager.INVALID_SUBSCRIPTION_ID)) + .anyMatch(s -> { + try { + return isCallOngoingFromState(mTelephonyManager + .createForSubscriptionId(s.getSubscriptionId()) + .getCallStateForSubscription()); + } catch (UnsupportedOperationException e) { + return false; + } + }); } private void updateTelephonyListeners() { diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt new file mode 100644 index 000000000000..c82ce8a4cb1d --- /dev/null +++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt @@ -0,0 +1,45 @@ +/* + * 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.server.wm.flicker.service.transitions.flicker + +import android.tools.Rotation +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.transitions.scenarios.LaunchTask +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class LaunchTaskPortrait : LaunchTask(Rotation.ROTATION_0) { + @ExpectedScenarios(["TASK_TRANSITION_SCENARIO", "OPEN_NEW_TASK_APP_SCENARIO"]) + @Test + override fun openNewTask() = super.openNewTask() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig() + .use(FlickerServiceConfig.DEFAULT) + .use(TASK_TRANSITION_SCENARIO) + .use(OPEN_NEW_TASK_APP_SCENARIO) + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt new file mode 100644 index 000000000000..147477d728be --- /dev/null +++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt @@ -0,0 +1,130 @@ +/* + * 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.server.wm.flicker.service.transitions.scenarios + +import android.app.Instrumentation +import android.tools.Rotation +import android.tools.flicker.AssertionInvocationGroup +import android.tools.flicker.assertors.assertions.AppWindowCoversFullScreenAtStart +import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd +import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart +import android.tools.flicker.assertors.assertions.BackgroundShowsInTransition +import android.tools.flicker.assertors.assertions.LayerBecomesInvisible +import android.tools.flicker.assertors.assertions.LayerBecomesVisible +import android.tools.flicker.assertors.assertions.LayerIsNeverVisible +import android.tools.flicker.assertors.assertions.AppWindowIsNeverVisible +import android.tools.flicker.config.AssertionTemplates +import android.tools.flicker.config.FlickerConfigEntry +import android.tools.flicker.config.ScenarioId +import android.tools.flicker.config.appclose.Components.CLOSING_APPS +import android.tools.flicker.config.appclose.Components.CLOSING_CHANGES +import android.tools.flicker.config.applaunch.Components.OPENING_CHANGES +import android.tools.flicker.config.common.Components.LAUNCHER +import android.tools.flicker.config.common.Components.WALLPAPER +import android.tools.flicker.extractors.TaggedCujTransitionMatcher +import android.tools.flicker.extractors.TaggedScenarioExtractorBuilder +import android.tools.flicker.rules.ChangeDisplayOrientationRule +import android.tools.traces.events.CujType +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.NewTasksAppHelper +import org.junit.After +import org.junit.Before +import org.junit.Test + + +/** + * This tests performs a transition between tasks + */ +abstract class LaunchTask(val rotation: Rotation = Rotation.ROTATION_0) { + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val tapl = LauncherInstrumentation() + + private val launchNewTaskApp = NewTasksAppHelper(instrumentation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + ChangeDisplayOrientationRule.setRotation(rotation) + tapl.setExpectedRotation(rotation.value) + launchNewTaskApp.launchViaIntent(wmHelper) + } + + @Test + open fun openNewTask() { + launchNewTaskApp.openNewTask(device, wmHelper) + } + + @After + fun tearDown() { + launchNewTaskApp.exit(wmHelper) + } + + companion object { + /** + * General task transition scenario that can be reused for any trace + */ + val TASK_TRANSITION_SCENARIO = + FlickerConfigEntry( + scenarioId = ScenarioId("TASK_TRANSITION_SCENARIO"), + extractor = TaggedScenarioExtractorBuilder() + .setTargetTag(CujType.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION) + .setTransitionMatcher( + TaggedCujTransitionMatcher(associatedTransitionRequired = true) + ) + .build(), + assertions = listOf( + // Opening changes replace the closing ones + LayerBecomesInvisible(CLOSING_CHANGES), + AppWindowOnTopAtStart(CLOSING_CHANGES), + LayerBecomesVisible(OPENING_CHANGES), + AppWindowOnTopAtEnd(OPENING_CHANGES), + + // There is a background color and it's covering the transition area + BackgroundShowsInTransition(CLOSING_CHANGES) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }) + ) + + /** + * Scenario that is making assertions that are valid for the new task app but that do not + * apply to other task transitions in general + */ + val OPEN_NEW_TASK_APP_SCENARIO = + FlickerConfigEntry( + scenarioId = ScenarioId("OPEN_NEW_TASK_APP_SCENARIO"), + extractor = TaggedScenarioExtractorBuilder() + .setTargetTag(CujType.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION) + .setTransitionMatcher( + TaggedCujTransitionMatcher(associatedTransitionRequired = true) + ) + .build(), + assertions = AssertionTemplates.COMMON_ASSERTIONS + + listOf( + // Wallpaper and launcher never visible + LayerIsNeverVisible(WALLPAPER, mustExist = true), + LayerIsNeverVisible(LAUNCHER, mustExist = true), + AppWindowIsNeverVisible(LAUNCHER, mustExist = true), + // App window covers the display at start + AppWindowCoversFullScreenAtStart(CLOSING_APPS) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }) + ) + } +}
\ No newline at end of file diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index 1f0bd61b5c3f..168141bf6e7d 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -19,6 +19,9 @@ android_test { "src/**/*.kt", ], asset_dirs: ["assets"], + kotlincflags: [ + "-Werror", + ], platform_apis: true, certificate: "platform", static_libs: [ diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 7a19add1d1b9..8cd89ce89e83 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -34,6 +34,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.Map; import java.util.Objects; @@ -73,15 +74,21 @@ public class TestableLooper { /** * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection. */ - private static boolean newTestabilityApisSupported() { - return android.os.Flags.messageQueueTestability(); + private static boolean isAtLeastBaklava() { + Method[] methods = TestLooperManager.class.getMethods(); + for (Method method : methods) { + if (method.getName().equals("peekWhen")) { + return true; + } + } + return false; // TODO(shayba): delete the above, uncomment the below. // SDK_INT has not yet ramped to Baklava in all 25Q2 builds. // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA; } static { - if (newTestabilityApisSupported()) { + if (isAtLeastBaklava()) { MESSAGE_QUEUE_MESSAGES_FIELD = null; MESSAGE_NEXT_FIELD = null; MESSAGE_WHEN_FIELD = null; @@ -241,14 +248,14 @@ public class TestableLooper { } public void moveTimeForward(long milliSeconds) { - if (newTestabilityApisSupported()) { - moveTimeForwardModern(milliSeconds); + if (isAtLeastBaklava()) { + moveTimeForwardBaklava(milliSeconds); } else { moveTimeForwardLegacy(milliSeconds); } } - private void moveTimeForwardModern(long milliSeconds) { + private void moveTimeForwardBaklava(long milliSeconds) { // Drain all Messages from the queue. Queue<Message> messages = new ArrayDeque<>(); while (true) { diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java index c7a36dd3f9d8..4d379e45a81a 100644 --- a/tests/utils/testutils/java/android/os/test/TestLooper.java +++ b/tests/utils/testutils/java/android/os/test/TestLooper.java @@ -65,13 +65,19 @@ public class TestLooper { private AutoDispatchThread mAutoDispatchThread; /** - * Modern introduces new {@link TestLooperManager} APIs that we can use instead of reflection. + * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection. */ - private static boolean newTestabilityApisSupported() { - return android.os.Flags.messageQueueTestability(); + private static boolean isAtLeastBaklava() { + Method[] methods = TestLooperManager.class.getMethods(); + for (Method method : methods) { + if (method.getName().equals("peekWhen")) { + return true; + } + } + return false; // TODO(shayba): delete the above, uncomment the below. - // SDK_INT has not yet ramped to Modern in all 25Q2 builds. - // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Modern; + // SDK_INT has not yet ramped to Baklava in all 25Q2 builds. + // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA; } static { @@ -81,7 +87,7 @@ public class TestLooper { THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); - if (newTestabilityApisSupported()) { + if (isAtLeastBaklava()) { MESSAGE_QUEUE_MESSAGES_FIELD = null; MESSAGE_NEXT_FIELD = null; MESSAGE_WHEN_FIELD = null; @@ -130,7 +136,7 @@ public class TestLooper { throw new RuntimeException("Reflection error constructing or accessing looper", e); } - if (newTestabilityApisSupported()) { + if (isAtLeastBaklava()) { mTestLooperManager = InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper); } else { @@ -159,14 +165,14 @@ public class TestLooper { } public void moveTimeForward(long milliSeconds) { - if (newTestabilityApisSupported()) { - moveTimeForwardModern(milliSeconds); + if (isAtLeastBaklava()) { + moveTimeForwardBaklava(milliSeconds); } else { moveTimeForwardLegacy(milliSeconds); } } - private void moveTimeForwardModern(long milliSeconds) { + private void moveTimeForwardBaklava(long milliSeconds) { // Drain all Messages from the queue. Queue<Message> messages = new ArrayDeque<>(); while (true) { @@ -259,14 +265,14 @@ public class TestLooper { * @return true if there are pending messages in the message queue */ public boolean isIdle() { - if (newTestabilityApisSupported()) { - return isIdleModern(); + if (isAtLeastBaklava()) { + return isIdleBaklava(); } else { return isIdleLegacy(); } } - private boolean isIdleModern() { + private boolean isIdleBaklava() { Long when = mTestLooperManager.peekWhen(); return when != null && currentTime() >= when; } @@ -280,14 +286,14 @@ public class TestLooper { * @return the next message in the Looper's message queue or null if there is none */ public Message nextMessage() { - if (newTestabilityApisSupported()) { - return nextMessageModern(); + if (isAtLeastBaklava()) { + return nextMessageBaklava(); } else { return nextMessageLegacy(); } } - private Message nextMessageModern() { + private Message nextMessageBaklava() { if (isIdle()) { return mTestLooperManager.poll(); } else { @@ -308,14 +314,14 @@ public class TestLooper { * Asserts that there is a message in the queue */ public void dispatchNext() { - if (newTestabilityApisSupported()) { - dispatchNextModern(); + if (isAtLeastBaklava()) { + dispatchNextBaklava(); } else { dispatchNextLegacy(); } } - private void dispatchNextModern() { + private void dispatchNextBaklava() { assertTrue(isIdle()); Message msg = mTestLooperManager.poll(); if (msg == null) { diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp index adf711ecfcbb..7bea96c26990 100644 --- a/tools/aapt2/link/FlaggedResources_test.cpp +++ b/tools/aapt2/link/FlaggedResources_test.cpp @@ -169,4 +169,18 @@ TEST_F(FlaggedResourcesTest, EnabledXmlELementAttributeRemoved) { ASSERT_TRUE(output.contains("test.package.readWriteFlag")); } +TEST_F(FlaggedResourcesTest, ReadWriteFlagInPathFails) { + test::TestDiagnosticsImpl diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + ASSERT_FALSE(CompileFile(GetTestPath("res/values/flag(!test.package.rwFlag)/bools.xml"), + R"(<resources> + <bool name="bool1">false</bool> + </resources>)", + compiled_files_dir, &diag, + {"--feature-flags", "test.package.rwFlag=false"})); + + ASSERT_TRUE(diag.GetLog().contains( + "Only read only flags may be used with resources: test.package.rwFlag")); +} + } // namespace aapt |